libeufin

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

commit 83451e306b83615b70b5d5e541fdcb965862ef0e
parent 341bd2ae1c5654321f893d7e5ac2e282425184e3
Author: MS <ms@taler.net>
Date:   Fri, 27 Oct 2023 12:47:53 +0200

nexus ebics-setup

only relying to the account metadata found in the
configuration, as per last DD 50 version.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 100++++++++++++-------------------------------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 31+++++++------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt | 25+++++++++++++++++++++++++
Mnexus/src/test/kotlin/PostFinance.kt | 20+++++++++++++++++++-
4 files changed, 66 insertions(+), 110 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -347,57 +347,6 @@ private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) { } /** - * Extracts bank account information and stores it to the bank account - * metadata file that's found in the configuration. It returns void in - * case of success, or fails the whole process upon errors. - * - * @param cfg configuration handle. - * @param bankAccounts bank response to EBICS HTD request. - * @param showAssociatedAccounts if true, the function only shows the - * users account, without persisting anything to disk. - */ -fun extractBankAccountMetadata( - cfg: EbicsSetupConfig, - bankAccounts: HTDResponseOrderData, - showAssociatedAccounts: Boolean -) { - // Now trying to extract whatever IBAN & BIC pair the bank gave in the response. - val foundIban: String? = findIban(bankAccounts.partnerInfo.accountInfoList) - val foundBic: String? = findBic(bankAccounts.partnerInfo.accountInfoList) - // _some_ IBAN & BIC _might_ have been found, compare it with the config. - if (foundIban == null) - logger.warn("Bank seems NOT to show any IBAN for our account.") - if (foundBic == null) - logger.warn("Bank seems NOT to show any BIC for our account.") - // Warn the user if instead one IBAN was found but that differs from the config. - if (foundIban != null && foundIban != cfg.accountNumber) { - logger.error("Bank has another IBAN for us: $foundIban, while config has: ${cfg.accountNumber}") - exitProcess(1) - } - // Users wants to only _see_ the accounts, NOT checking values and returning here. - if (showAssociatedAccounts) { - println("Bank associates this account to the EBICS user ${cfg.ebicsUserId}: IBAN: $foundIban, BIC: $foundBic, Name: ${bankAccounts.userInfo.name}") - return - } - // No divergences were found, either because the config was right - // _or_ the bank didn't give any information. Setting the account - // metadata accordingly. - val accountMetaData = BankAccountMetadataFile( - account_holder_name = bankAccounts.userInfo.name ?: "Account holder name not given", - account_holder_iban = foundIban ?: run iban@ { - logger.warn("Bank did not show any IBAN for us, defaulting to the one we configured.") - return@iban cfg.accountNumber }, - bank_code = foundBic ?: run bic@ { - logger.warn("Bank did not show any BIC for us, setting it as null.") - return@bic null } - ) - if (!syncJsonToDisk(accountMetaData, cfg.bankAccountMetadataFilename)) { - logger.error("Failed to persist bank account meta-data at: ${cfg.bankAccountMetadataFilename}") - exitProcess(1) - } -} - -/** * CLI class implementing the "ebics-setup" subcommand. */ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { @@ -481,7 +430,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { if (keysNotSub || generateRegistrationPdf) makePdf(privs, cfg) // Checking if the bank keys exist on disk. val bankKeysFile = File(cfg.bankPublicKeysFilename) - if (!bankKeysFile.exists()) { // FIXME: should this also check the content validity? + if (!bankKeysFile.exists()) { val areKeysOnDisk = runBlocking { doKeysRequestAndUpdateState( cfg, @@ -502,43 +451,24 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { logger.error("Although previous checks, could not load the bank keys file from: ${cfg.bankPublicKeysFilename}") exitProcess(1) } - /** - * The following block potentially updates the bank keys state - * on disk, if that's the first time that they become accepted. - * If so, finally reloads the bank keys file from disk. - */ - val bankKeys = if (!bankKeysMaybe.accepted) { - - if (autoAcceptKeys) bankKeysMaybe.accepted = true - else bankKeysMaybe.accepted = askUserToAcceptKeys(bankKeysMaybe) - - if (!bankKeysMaybe.accepted) { - logger.error("Cannot continue without accepting the bank keys.") - exitProcess(1) - } + val printOk = { println("setup ready") } - if (!syncJsonToDisk(bankKeysMaybe, cfg.bankPublicKeysFilename)) { - logger.error("Could not set bank keys as accepted on disk.") - exitProcess(1) - } - // Reloading after the disk write above. - loadBankKeys(cfg.bankPublicKeysFilename) ?: kotlin.run { - logger.error("Could not reload bank keys after disk write.") - exitProcess(1) - } - } else - bankKeysMaybe // keys were already accepted. + if (bankKeysMaybe.accepted) { + printOk() + return + } + // Finishing the setup by accepting the bank keys. + if (autoAcceptKeys) bankKeysMaybe.accepted = true + else bankKeysMaybe.accepted = askUserToAcceptKeys(bankKeysMaybe) - // Downloading the list of owned bank account(s). - val bankAccounts = runBlocking { - fetchBankAccounts(cfg, privs, bankKeys, httpClient) + if (!bankKeysMaybe.accepted) { + logger.error("Cannot successfully finish the setup without accepting the bank keys.") + exitProcess(1) } - if (bankAccounts == null) { - logger.error("Could not obtain the list of bank accounts from the bank.") + if (!syncJsonToDisk(bankKeysMaybe, cfg.bankPublicKeysFilename)) { + logger.error("Could not set bank keys as accepted on disk.") exitProcess(1) } - logger.info("Subscriber's bank accounts fetched.") - extractBankAccountMetadata(cfg, bankAccounts, showAssociatedAccounts) - println("setup ready") + printOk() } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -101,10 +101,14 @@ class EbicsSetupConfig(val config: TalerConfig) { */ val ebicsSystemId = ebicsSetupRequireString("system_id") /** - * Bank account name, as given by the bank. It - * can be an IBAN or even any alphanumeric value. + * Bank account metadata. */ - val accountNumber = ebicsSetupRequireString("account_number") + val accountNumber: IbanPayto = ebicsSetupRequireString("account_number").run { + val maybeAccount = parsePayto(this) + if (maybeAccount?.bic == null) throw Exception("ACCOUNT_NUMBER lacks the BIC") + if (maybeAccount.receiverName == null) throw Exception("ACCOUNT_NUMBER lacks the name") + return@run maybeAccount + } /** * Filename where we store the bank public keys. */ @@ -114,10 +118,6 @@ class EbicsSetupConfig(val config: TalerConfig) { */ val clientPrivateKeysFilename = ebicsSetupRequireString("client_private_keys_file") /** - * Filename where we store the bank account main information. - */ - val bankAccountMetadataFilename = ebicsSetupRequireString("account_meta_data_file") - /** * A name that identifies the EBICS and ISO20022 flavour * that Nexus should honor in the communication with the * bank. @@ -201,23 +201,6 @@ data class BankPublicKeysFile( ) /** - * Gets the bank account metadata file, according to the - * location found in the configuration. The caller may still - * have to handle the exception, in case the found file doesn't - * parse to the wanted JSON type. - * - * @param cfg configuration handle. - * @return [BankAccountMetadataFile] or null, if the file wasn't found. - */ -fun loadBankAccountFile(cfg: EbicsSetupConfig): BankAccountMetadataFile? { - val f = File(cfg.bankAccountMetadataFilename) - if (!f.exists()) { - logger.error("Bank account metadata file not found in ${cfg.bankAccountMetadataFilename}") - return null - } - return myJson.decodeFromString(f.readText()) -} -/** * Load the bank keys file from disk. * * @param location the keys file location. diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -35,6 +35,30 @@ import java.util.* import javax.xml.datatype.DatatypeFactory /** + * Convenience function to download via EBICS with a + * customer message type. + * + * @param messageType EBICS 2.x message type. Defaults + * to HTD, to get general information about the EBICS + * subscriber. + * @param cfg configuration handle + * @param clientKeys client EBICS keys. + * @param bankKeys bank EBICS keys. + * @param client HTTP client handle. + * @return raw XML response, or null upon errors. + */ +suspend fun doEbicsCustomDownload( + messageType: String = "HTD", + cfg: EbicsSetupConfig, + clientKeys: ClientPrivateKeysFile, + bankKeys: BankPublicKeysFile, + client: HttpClient +): String? { + val xmlReq = createEbics25DownloadInit(cfg, clientKeys, bankKeys, messageType) + return doEbicsDownload(client, cfg, clientKeys, bankKeys, xmlReq, false) +} + +/** * Request EBICS (2.x) HTD to the bank. This message type * gets the list of bank accounts that are owned by the EBICS * client. @@ -59,6 +83,7 @@ suspend fun fetchBankAccounts( return null } return try { + logger.debug("Fetched accounts: $xmlResp") XMLUtil.convertStringToJaxb<HTDResponseOrderData>(xmlResp).value } catch (e: Exception) { logger.error("Could not parse the HTD payload, detail: ${e.message}") diff --git a/nexus/src/test/kotlin/PostFinance.kt b/nexus/src/test/kotlin/PostFinance.kt @@ -3,6 +3,7 @@ import kotlinx.coroutines.runBlocking import org.junit.Ignore import org.junit.Test import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.doEbicsCustomDownload import tech.libeufin.nexus.ebics.fetchBankAccounts import tech.libeufin.nexus.ebics.submitPayment import tech.libeufin.util.IbanPayto @@ -50,7 +51,7 @@ class Iso20022 { } } -@Ignore +// @Ignore class PostFinance { // Tests sending client keys to the PostFinance test platform. @Test @@ -83,6 +84,23 @@ class PostFinance { } } + @Test + fun customDownload() { + val cfg = prep() + val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) + val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) + runBlocking { + val xml = doEbicsCustomDownload( + messageType = "HTD", + cfg = cfg, + bankKeys = bankKeys!!, + clientKeys = clientKeys!!, + client = HttpClient() + ) + println(xml) + } + } + // Tests the HTD message type. @Test fun fetchAccounts() {