gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] 01/02: nexus: moving subcommand into own file


From: gnunet
Subject: [libeufin] 01/02: nexus: moving subcommand into own file
Date: Fri, 20 Oct 2023 17:20:38 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

commit d290f8620da57ccf881166fc1f165873f7100b42
Author: MS <ms@taler.net>
AuthorDate: Fri Oct 20 15:22:21 2023 +0200

    nexus: moving subcommand into own file
---
 .../main/kotlin/tech/libeufin/nexus/Database.kt    |   2 +
 .../tech/libeufin/nexus/{Main.kt => EbicsSetup.kt} | 356 +++------------
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 485 +--------------------
 3 files changed, 59 insertions(+), 784 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
new file mode 100644
index 00000000..c786e98e
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -0,0 +1,2 @@
+package tech.libeufin.nexus
+
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
similarity index 70%
copy from nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
copy to nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index abd5044e..2125d0a0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -17,199 +17,24 @@
  * <http://www.gnu.org/licenses/>
  */
 
-/**
- * This file runs the main logic of nexus-setup.  This tool is
- * responsible for reading configuration values about an EBICS
- * subscriber and preparing the key material for further communication
- * with the bank.
- */
-
 package tech.libeufin.nexus
-import ConfigSource
-import TalerConfig
-import TalerConfigError
+
 import com.github.ajalt.clikt.core.CliktCommand
-import com.github.ajalt.clikt.core.subcommands
 import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.option
-import com.github.ajalt.clikt.parameters.options.versionOption
 import io.ktor.client.*
-import io.ktor.util.*
 import kotlinx.coroutines.runBlocking
-import kotlinx.serialization.Contextual
-import kotlinx.serialization.KSerializer
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
+import tech.libeufin.util.ebics_h004.EbicsTypes
 import java.io.File
 import kotlin.system.exitProcess
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.descriptors.PrimitiveKind
-import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
-import kotlinx.serialization.descriptors.SerialDescriptor
+import TalerConfig
+import TalerConfigError
 import kotlinx.serialization.encodeToString
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.SerializersModule
-import net.taler.wallet.crypto.Base32Crockford
-import org.slf4j.event.Level
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsTypes
-import java.security.interfaces.RSAPrivateCrtKey
-import java.security.interfaces.RSAPublicKey
 import java.time.Instant
 import kotlin.reflect.typeOf
 
-val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin-nexus", "libeufin-nexus")
-val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus.Main")
-val myJson = Json {
-    this.serializersModule = SerializersModule {
-        contextual(RSAPrivateCrtKey::class) { RSAPrivateCrtKeySerializer }
-        contextual(RSAPublicKey::class) { RSAPublicKeySerializer }
-    }
-}
-
-/**
- * Keeps all the options of the ebics-setup subcommand.  The
- * caller has to handle TalerConfigError if values are missing.
- * If even one of the fields could not be instantiated, then
- * throws TalerConfigError.
- */
-class EbicsSetupConfig(config: TalerConfig) {
-    // abstracts the section name.
-    private val ebicsSetupRequireString = { option: String ->
-        config.requireString("nexus-ebics", option)
-    }
-    // debug utility to inspect what was loaded.
-    fun _dump() {
-        this.javaClass.declaredFields.forEach {
-            println("cfg obj: ${it.name} -> ${it.get(this)}")
-        }
-    }
-    /**
-     * The bank's currency.
-     */
-    val currency = ebicsSetupRequireString("currency")
-    /**
-     * The bank base URL.
-     */
-    val hostBaseUrl = ebicsSetupRequireString("host_base_url")
-    /**
-     * The bank EBICS host ID.
-     */
-    val ebicsHostId = ebicsSetupRequireString("host_id")
-    /**
-     * EBICS user ID.
-     */
-    val ebicsUserId = ebicsSetupRequireString("user_id")
-    /**
-     * EBICS partner ID.
-     */
-    val ebicsPartnerId = ebicsSetupRequireString("partner_id")
-    /**
-     * EBICS system ID (is this optional?).
-     */
-    val ebicsSystemId = ebicsSetupRequireString("system_id")
-    /**
-     * Bank account name, as given by the bank.  It
-     * can be an IBAN or even any alphanumeric value.
-     */
-    val accountNumber = ebicsSetupRequireString("account_number")
-    /**
-     * Filename where we store the bank public keys.
-     */
-    val bankPublicKeysFilename = 
ebicsSetupRequireString("bank_public_keys_file")
-    /**
-     * Filename where we store our private keys.
-     */
-    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.
-     */
-    val bankDialect: String = ebicsSetupRequireString("bank_dialect").run {
-        if (this != "postfinance") throw Exception("Only 'postfinance' dialect 
is supported.")
-        return@run this
-    }
-}
-
-/**
- * Converts base 32 representation of RSA public keys and vice versa.
- */
-object RSAPublicKeySerializer : KSerializer<RSAPublicKey> {
-    override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("RSAPublicKey", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: RSAPublicKey) {
-        encoder.encodeString(Base32Crockford.encode(value.encoded))
-    }
-
-    // Caller must handle exceptions here.
-    override fun deserialize(decoder: Decoder): RSAPublicKey {
-        val fieldValue = decoder.decodeString()
-        val bytes = Base32Crockford.decode(fieldValue)
-        return CryptoUtil.loadRsaPublicKey(bytes)
-    }
-}
-
-/**
- * Converts base 32 representation of RSA private keys and vice versa.
- */
-object RSAPrivateCrtKeySerializer : KSerializer<RSAPrivateCrtKey> {
-    override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("RSAPrivateCrtKey", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: RSAPrivateCrtKey) {
-        encoder.encodeString(Base32Crockford.encode(value.encoded))
-    }
-
-    // Caller must handle exceptions here.
-    override fun deserialize(decoder: Decoder): RSAPrivateCrtKey {
-        val fieldValue = decoder.decodeString()
-        val bytes = Base32Crockford.decode(fieldValue)
-        return CryptoUtil.loadRsaPrivateKey(bytes)
-    }
-}
-
-/**
- * Structure of the file that holds the bank account
- * metadata.
- */
-@Serializable
-data class BankAccountMetadataFile(
-    val account_holder_iban: String,
-    val bank_code: String?,
-    val account_holder_name: String
-)
-
-/**
- * Structure of the JSON file that contains the client
- * private keys on disk.
- */
-@Serializable
-data class ClientPrivateKeysFile(
-    // FIXME: centralize the @Contextual use.
-    @Contextual val signature_private_key: RSAPrivateCrtKey,
-    @Contextual val encryption_private_key: RSAPrivateCrtKey,
-    @Contextual val authentication_private_key: RSAPrivateCrtKey,
-    var submitted_ini: Boolean,
-    var submitted_hia: Boolean
-)
-
-/**
- * Structure of the JSON file that contains the bank
- * public keys on disk.
- */
-@Serializable
-data class BankPublicKeysFile(
-    @Contextual val bank_encryption_public_key: RSAPublicKey,
-    @Contextual val bank_authentication_public_key: RSAPublicKey,
-    var accepted: Boolean
-)
 /**
  * Writes the JSON content to disk.  Used when we create or update
  * keys and other metadata JSON content to disk.  WARNING: this overrides
@@ -241,7 +66,7 @@ fun generateNewKeys(): ClientPrivateKeysFile =
         signature_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
         submitted_hia = false,
         submitted_ini = false
-)
+    )
 /**
  * Conditionally generates the client private keys and stores them
  * to disk, if the file does not exist already.  Does nothing if the
@@ -265,64 +90,6 @@ fun maybeCreatePrivateKeysFile(filename: String): Boolean {
     return true
 }
 
-/**
- * Load the bank keys file from disk.
- *
- * @param location the keys file location.
- * @return the internal JSON representation of the keys file,
- *         or null on failures.
- */
-fun loadBankKeys(location: String): BankPublicKeysFile? {
-    val f = File(location)
-    if (!f.exists()) {
-        logger.error("Could not find the bank keys file at: $location")
-        return null
-    }
-    val fileContent = try {
-        f.readText() // read from disk.
-    } catch (e: Exception) {
-        logger.error("Could not read the bank keys file from disk, detail: 
${e.message}")
-        return null
-    }
-    return try {
-        myJson.decodeFromString(fileContent) // Parse into JSON.
-    } catch (e: Exception) {
-        logger.error(e.message)
-        @OptIn(InternalAPI::class) // enables message below.
-        logger.error(e.rootCause?.message) // actual useful message mentioning 
failing fields
-        return null
-    }
-}
-
-/**
- * Load the client keys file from disk.
- *
- * @param location the keys file location.
- * @return the internal JSON representation of the keys file,
- *         or null on failures.
- */
-fun loadPrivateKeysFromDisk(location: String): ClientPrivateKeysFile? {
-    val f = File(location)
-    if (!f.exists()) {
-        logger.error("Could not find the private keys file at: $location")
-        return null
-    }
-    val fileContent = try {
-        f.readText() // read from disk.
-    } catch (e: Exception) {
-        logger.error("Could not read private keys from disk, detail: 
${e.message}")
-        return null
-    }
-    return try {
-        myJson.decodeFromString(fileContent) // Parse into JSON.
-    } catch (e: Exception) {
-        logger.error(e.message)
-        @OptIn(InternalAPI::class) // enables message below.
-        logger.error(e.rootCause?.message) // actual useful message mentioning 
failing fields
-        return null
-    }
-}
-
 /**
  * Obtains the client private keys, regardless of them being
  * created for the first time, or read from an existing file
@@ -331,7 +98,7 @@ fun loadPrivateKeysFromDisk(location: String): 
ClientPrivateKeysFile? {
  * @param location path to the file that contains the keys.
  * @return true if the operation succeeds, false otherwise.
  */
-fun preparePrivateKeys(location: String): ClientPrivateKeysFile? {
+private fun preparePrivateKeys(location: String): ClientPrivateKeysFile? {
     if (!maybeCreatePrivateKeysFile(location)) {
         logger.error("Could not create client keys at $location")
         exitProcess(1)
@@ -369,7 +136,7 @@ fun String.spaceEachTwo() =
  * @param bankKeys bank public keys, in format stored on disk.
  * @return true if the user accepted, false otherwise.
  */
-fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean {
+private fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean {
     val encHash = 
CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).toHexString()
     val authHash = 
CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_authentication_public_key).toHexString()
     println("The bank has the following keys, type 'yes, accept' to accept 
them..\n")
@@ -497,53 +264,6 @@ private fun maybeExtractIban(accountNumberList: 
List<EbicsTypes.AbstractAccountN
 private fun maybeExtractBic(bankCodes: List<EbicsTypes.AbstractBankCode>): 
String? =
     bankCodes.filterIsInstance<EbicsTypes.GeneralBankCode>().find { 
it.international }?.value
 
-/**
- * Mere collector of the PDF generation steps.  Fails the
- * process if a problem occurs.
- *
- * @param privs client private keys.
- * @param cfg configuration handle.
- */
-private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) {
-    val pdf = generateKeysPdf(privs, cfg)
-    val pdfFile = 
File("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
-    if (pdfFile.exists()) {
-        logger.error("PDF file exists already at: ${pdfFile.path}, not 
overriding it")
-        exitProcess(1)
-    }
-    try {
-        pdfFile.writeBytes(pdf)
-    } catch (e: Exception) {
-        logger.error("Could not write PDF to ${pdfFile}, detail: ${e.message}")
-        exitProcess(1)
-    }
-    println("PDF file with keys hex encoding created at: $pdfFile")
-}
-
-/**
- * Mere collector of the steps to load and parse the config.
- *
- * @param configFile location of the configuration entry point.
- * @return internal representation of the configuration.
- */
-private fun extractConfig(configFile: String?): EbicsSetupConfig {
-    val config = TalerConfig(NEXUS_CONFIG_SOURCE)
-    try {
-        config.load(configFile)
-    } catch (e: Exception) {
-        logger.error("Could not load configuration from ${configFile}, detail: 
${e.message}")
-        exitProcess(1)
-    }
-    // Checking the config.
-    val cfg = try {
-        EbicsSetupConfig(config)
-    } catch (e: TalerConfigError) {
-        logger.error(e.message)
-        exitProcess(1)
-    }
-    return cfg
-}
-
 private fun findIban(maybeList: List<EbicsTypes.AccountInfo>?): String? {
     if (maybeList == null) {
         logger.warn("Looking for IBAN: bank did not give any account list for 
us.")
@@ -585,6 +305,53 @@ private fun findBic(maybeList: 
List<EbicsTypes.AccountInfo>?): String? {
     return maybeExtractBic(bankCodeList)
 }
 
+/**
+ * Mere collector of the steps to load and parse the config.
+ *
+ * @param configFile location of the configuration entry point.
+ * @return internal representation of the configuration.
+ */
+private fun extractConfig(configFile: String?): EbicsSetupConfig {
+    val config = TalerConfig(NEXUS_CONFIG_SOURCE)
+    try {
+        config.load(configFile)
+    } catch (e: Exception) {
+        logger.error("Could not load configuration from ${configFile}, detail: 
${e.message}")
+        exitProcess(1)
+    }
+    // Checking the config.
+    val cfg = try {
+        EbicsSetupConfig(config)
+    } catch (e: TalerConfigError) {
+        logger.error(e.message)
+        exitProcess(1)
+    }
+    return cfg
+}
+
+/**
+ * Mere collector of the PDF generation steps.  Fails the
+ * process if a problem occurs.
+ *
+ * @param privs client private keys.
+ * @param cfg configuration handle.
+ */
+private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) {
+    val pdf = generateKeysPdf(privs, cfg)
+    val pdfFile = 
File("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
+    if (pdfFile.exists()) {
+        logger.error("PDF file exists already at: ${pdfFile.path}, not 
overriding it")
+        exitProcess(1)
+    }
+    try {
+        pdfFile.writeBytes(pdf)
+    } catch (e: Exception) {
+        logger.error("Could not write PDF to ${pdfFile}, detail: ${e.message}")
+        exitProcess(1)
+    }
+    println("PDF file with keys hex encoding created at: $pdfFile")
+}
+
 /**
  * CLI class implementing the "ebics-setup" subcommand.
  */
@@ -744,19 +511,4 @@ class EbicsSetup: CliktCommand() {
         }
         println("setup ready")
     }
-}
-
-/**
- * Main CLI class that collects all the subcommands.
- */
-class LibeufinNexusCommand : CliktCommand() {
-    init {
-        versionOption(getVersion())
-        subcommands(EbicsSetup())
-    }
-    override fun run() = Unit
-}
-
-fun main(args: Array<String>) {
-    LibeufinNexusCommand().main(args)
 }
\ 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
index abd5044e..b148c4b2 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -18,12 +18,10 @@
  */
 
 /**
- * This file runs the main logic of nexus-setup.  This tool is
- * responsible for reading configuration values about an EBICS
- * subscriber and preparing the key material for further communication
- * with the bank.
+ * This file collects all the CLI subcommands and runs
+ * them.  The actual implementation of each subcommand is
+ * kept in their respective files.
  */
-
 package tech.libeufin.nexus
 import ConfigSource
 import TalerConfig
@@ -210,60 +208,6 @@ data class BankPublicKeysFile(
     @Contextual val bank_authentication_public_key: RSAPublicKey,
     var accepted: Boolean
 )
-/**
- * Writes the JSON content to disk.  Used when we create or update
- * keys and other metadata JSON content to disk.  WARNING: this overrides
- * silently what's found under the given location!
- *
- * @param obj the class representing the JSON content to store to disk.
- * @param location where to store `obj`
- * @return true in case of success, false otherwise.
- */
-inline fun <reified T> syncJsonToDisk(obj: T, location: String): Boolean {
-    val fileContent = try {
-        myJson.encodeToString(obj)
-    } catch (e: Exception) {
-        logger.error("Could not encode the input '${typeOf<T>()}' to JSON, 
detail: ${e.message}")
-        return false
-    }
-    try {
-        File(location).writeText(fileContent)
-    } catch (e: Exception) {
-        logger.error("Could not write JSON content at $location, detail: 
${e.message}")
-        return false
-    }
-    return true
-}
-fun generateNewKeys(): ClientPrivateKeysFile =
-    ClientPrivateKeysFile(
-        authentication_private_key = 
CryptoUtil.generateRsaKeyPair(2048).private,
-        encryption_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
-        signature_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
-        submitted_hia = false,
-        submitted_ini = false
-)
-/**
- * Conditionally generates the client private keys and stores them
- * to disk, if the file does not exist already.  Does nothing if the
- * file exists.
- *
- * @param filename keys file location
- * @return true if the keys file existed already or its creation
- *         went through, false for any error.
- */
-fun maybeCreatePrivateKeysFile(filename: String): Boolean {
-    val f = File(filename)
-    // NOT overriding any file at the wanted location.
-    if (f.exists()) {
-        logger.debug("Private key file found at: $filename.")
-        return true
-    }
-    val newKeys = generateNewKeys()
-    if (!syncJsonToDisk(newKeys, filename))
-        return false
-    logger.info("New client keys created at: $filename")
-    return true
-}
 
 /**
  * Load the bank keys file from disk.
@@ -323,429 +267,6 @@ fun loadPrivateKeysFromDisk(location: String): 
ClientPrivateKeysFile? {
     }
 }
 
-/**
- * Obtains the client private keys, regardless of them being
- * created for the first time, or read from an existing file
- * on disk.
- *
- * @param location path to the file that contains the keys.
- * @return true if the operation succeeds, false otherwise.
- */
-fun preparePrivateKeys(location: String): ClientPrivateKeysFile? {
-    if (!maybeCreatePrivateKeysFile(location)) {
-        logger.error("Could not create client keys at $location")
-        exitProcess(1)
-    }
-    return loadPrivateKeysFromDisk(location) // loads what found at location.
-}
-
-/**
- * Expresses the type of keying message that the user wants
- * to send to the bank.
- */
-enum class KeysOrderType {
-    INI,
-    HIA,
-    HPB
-}
-
-/**
- * @return the "this" string with a space every two characters.
- */
-fun String.spaceEachTwo() =
-    buildString {
-        this@spaceEachTwo.forEachIndexed { pos, c ->
-            when {
-                (pos == 0) -> this.append(c)
-                (pos % 2 == 0) -> this.append(" $c")
-                else -> this.append(c)
-            }
-        }
-    }
-
-/**
- * Asks the user to accept the bank public keys.
- *
- * @param bankKeys bank public keys, in format stored on disk.
- * @return true if the user accepted, false otherwise.
- */
-fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean {
-    val encHash = 
CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).toHexString()
-    val authHash = 
CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_authentication_public_key).toHexString()
-    println("The bank has the following keys, type 'yes, accept' to accept 
them..\n")
-    println("Encryption key: ${encHash.spaceEachTwo()}")
-    println("Authentication key: ${authHash.spaceEachTwo()}")
-    val userResponse: String? = readlnOrNull()
-    if (userResponse == "yes, accept")
-        return true
-    return false
-}
-
-/**
- * Parses the HPB response and stores the bank keys as "NOT accepted" to disk.
- *
- * @param cfg used to get the location of the bank keys file.
- * @param bankKeys bank response to the HPB message.
- * @return true if the keys were stored to disk (as "not accepted"),
- *         false if the storage failed or the content was invalid.
- */
-private fun handleHpbResponse(
-    cfg: EbicsSetupConfig,
-    bankKeys: EbicsKeyManagementResponseContent
-): Boolean {
-    val hpbBytes = bankKeys.orderData // silences compiler.
-    if (hpbBytes == null) {
-        logger.error("HPB content not found in a EBICS response with 
successful return codes.")
-        return false
-    }
-    val hpbObj = try {
-        parseEbicsHpbOrder(hpbBytes)
-    }
-    catch (e: Exception) {
-        logger.error("HPB response content seems invalid.")
-        return false
-    }
-    val encPub = try {
-        CryptoUtil.loadRsaPublicKey(hpbObj.encryptionPubKey.encoded)
-    } catch (e: Exception) {
-        logger.error("Could not import bank encryption key from HPB response, 
detail: ${e.message}")
-        return false
-    }
-    val authPub = try {
-        CryptoUtil.loadRsaPublicKey(hpbObj.authenticationPubKey.encoded)
-    } catch (e: Exception) {
-        logger.error("Could not import bank authentication key from HPB 
response, detail: ${e.message}")
-        return false
-    }
-    val json = BankPublicKeysFile(
-        bank_authentication_public_key = authPub,
-        bank_encryption_public_key = encPub,
-        accepted = false
-    )
-    if (!syncJsonToDisk(json, cfg.bankPublicKeysFilename)) {
-        logger.error("Failed to persist the bank keys to disk at: 
${cfg.bankPublicKeysFilename}")
-        return false
-    }
-    return true
-}
-
-/**
- * Collects all the steps from generating the message, to
- * sending it to the bank, and finally updating the state
- * on disk according to the response.
- *
- * @param cfg handle to the configuration.
- * @param privs bundle of all the private keys of the client.
- * @param client the http client that requests to the bank.
- * @param orderType INI or HIA.
- * @param autoAcceptBankKeys only given in case of HPB.  Expresses
- *        the --auto-accept-key CLI flag.
- * @return true if the message fulfilled its purpose AND the state
- *         on disk was accordingly updated, or false otherwise.
- */
-suspend fun doKeysRequestAndUpdateState(
-    cfg: EbicsSetupConfig,
-    privs: ClientPrivateKeysFile,
-    client: HttpClient,
-    orderType: KeysOrderType
-): Boolean {
-    val req = when(orderType) {
-        KeysOrderType.INI -> generateIniMessage(cfg, privs)
-        KeysOrderType.HIA -> generateHiaMessage(cfg, privs)
-        KeysOrderType.HPB -> generateHpbMessage(cfg, privs)
-    }
-    val xml = client.postToBank(cfg.hostBaseUrl, req)
-    if (xml == null) {
-        logger.error("Could not POST the ${orderType.name} message to the 
bank")
-        return false
-    }
-    val ebics = parseKeysMgmtResponse(privs.encryption_private_key, xml)
-    if (ebics == null) {
-        logger.error("Could not get any EBICS from the bank ${orderType.name} 
response ($xml).")
-        return false
-    }
-    if (ebics.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
-        logger.error("EBICS ${orderType.name} failed with code: 
${ebics.technicalReturnCode}")
-        return false
-    }
-    if (ebics.bankReturnCode != EbicsReturnCode.EBICS_OK) {
-        logger.error("EBICS ${orderType.name} reached the bank, but could not 
be fulfilled, error code: ${ebics.bankReturnCode}")
-        return false
-    }
-
-    when(orderType) {
-        KeysOrderType.INI -> privs.submitted_ini = true
-        KeysOrderType.HIA -> privs.submitted_hia = true
-        KeysOrderType.HPB -> return handleHpbResponse(cfg, ebics)
-    }
-    if (!syncJsonToDisk(privs, cfg.clientPrivateKeysFilename)) {
-        logger.error("Could not update the ${orderType.name} state on disk")
-        return false
-    }
-    return true
-}
-
-/**
- * Abstracts (part of) the IBAN extraction from an HTD response.
- */
-private fun maybeExtractIban(accountNumberList: 
List<EbicsTypes.AbstractAccountNumber>): String? =
-    accountNumberList.filterIsInstance<EbicsTypes.GeneralAccountNumber>().find 
{ it.international }?.value
-
-/**
- * Abstracts (part of) the BIC extraction from an HTD response.
- */
-private fun maybeExtractBic(bankCodes: List<EbicsTypes.AbstractBankCode>): 
String? =
-    bankCodes.filterIsInstance<EbicsTypes.GeneralBankCode>().find { 
it.international }?.value
-
-/**
- * Mere collector of the PDF generation steps.  Fails the
- * process if a problem occurs.
- *
- * @param privs client private keys.
- * @param cfg configuration handle.
- */
-private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) {
-    val pdf = generateKeysPdf(privs, cfg)
-    val pdfFile = 
File("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
-    if (pdfFile.exists()) {
-        logger.error("PDF file exists already at: ${pdfFile.path}, not 
overriding it")
-        exitProcess(1)
-    }
-    try {
-        pdfFile.writeBytes(pdf)
-    } catch (e: Exception) {
-        logger.error("Could not write PDF to ${pdfFile}, detail: ${e.message}")
-        exitProcess(1)
-    }
-    println("PDF file with keys hex encoding created at: $pdfFile")
-}
-
-/**
- * Mere collector of the steps to load and parse the config.
- *
- * @param configFile location of the configuration entry point.
- * @return internal representation of the configuration.
- */
-private fun extractConfig(configFile: String?): EbicsSetupConfig {
-    val config = TalerConfig(NEXUS_CONFIG_SOURCE)
-    try {
-        config.load(configFile)
-    } catch (e: Exception) {
-        logger.error("Could not load configuration from ${configFile}, detail: 
${e.message}")
-        exitProcess(1)
-    }
-    // Checking the config.
-    val cfg = try {
-        EbicsSetupConfig(config)
-    } catch (e: TalerConfigError) {
-        logger.error(e.message)
-        exitProcess(1)
-    }
-    return cfg
-}
-
-private fun findIban(maybeList: List<EbicsTypes.AccountInfo>?): String? {
-    if (maybeList == null) {
-        logger.warn("Looking for IBAN: bank did not give any account list for 
us.")
-        return null
-    }
-    if (maybeList.size != 1) {
-        logger.warn("Looking for IBAN: bank gave account list, but it was not 
a singleton.")
-        return null
-    }
-    val accountNumberList = maybeList[0].accountNumberList
-    if (accountNumberList == null) {
-        logger.warn("Bank gave account list, but no IBAN list of found.")
-        return null
-    }
-    if (accountNumberList.size != 1) {
-        logger.warn("Bank gave account list, but IBAN list was not singleton.")
-        return null
-    }
-    return maybeExtractIban(accountNumberList)
-}
-private fun findBic(maybeList: List<EbicsTypes.AccountInfo>?): String? {
-    if (maybeList == null) {
-        logger.warn("Looking for BIC: bank did not give any account list for 
us.")
-        return null
-    }
-    if (maybeList.size != 1) {
-        logger.warn("Looking for BIC: bank gave account list, but it was not a 
singleton.")
-        return null
-    }
-    val bankCodeList = maybeList[0].bankCodeList
-    if (bankCodeList == null) {
-        logger.warn("Bank gave account list, but no BIC list of found.")
-        return null
-    }
-    if (bankCodeList.size != 1) {
-        logger.warn("Bank gave account list, but BIC list was not singleton.")
-        return null
-    }
-    return maybeExtractBic(bankCodeList)
-}
-
-/**
- * CLI class implementing the "ebics-setup" subcommand.
- */
-class EbicsSetup: CliktCommand() {
-    private val configFile by option(
-        "--config", "-c",
-        help = "set the configuration file"
-    )
-    private val checkFullConfig by option(
-        help = "checks config values of ALL the subcommands"
-    ).flag(default = false)
-    private val forceKeysResubmission by option(
-        help = "resubmits all the keys to the bank"
-    ).flag(default = false)
-    private val autoAcceptKeys by option(
-        help = "accepts the bank keys without the user confirmation"
-    ).flag(default = false)
-    private val generateRegistrationPdf by option(
-        help = "generates the PDF with the client public keys to send to the 
bank"
-    ).flag(default = false)
-    private val showAssociatedAccounts by option(
-        help = "shows which bank accounts belong to the EBICS subscriber"
-    ).flag(default = false)
-
-    /**
-     * This function collects the main steps of setting up an EBICS access.
-     */
-    override fun run() {
-        val cfg = extractConfig(this.configFile)
-        if (checkFullConfig) {
-            throw NotImplementedError("--check-full-config flag not 
implemented")
-        }
-        // Config is sane.  Go (maybe) making the private keys.
-        val privsMaybe = preparePrivateKeys(cfg.clientPrivateKeysFilename)
-        if (privsMaybe == null) {
-            logger.error("Private keys preparation failed.")
-            exitProcess(1)
-        }
-        val httpClient = HttpClient()
-        // Privs exist.  Upload their pubs
-        val keysNotSub = !privsMaybe.submitted_ini || !privsMaybe.submitted_hia
-        runBlocking {
-            if ((!privsMaybe.submitted_ini) || forceKeysResubmission)
-                doKeysRequestAndUpdateState(cfg, privsMaybe, httpClient, 
KeysOrderType.INI).apply { if (!this) exitProcess(1) }
-            if ((!privsMaybe.submitted_hia) || forceKeysResubmission)
-                doKeysRequestAndUpdateState(cfg, privsMaybe, httpClient, 
KeysOrderType.HIA).apply { if (!this) exitProcess(1) }
-        }
-        // Reloading new state from disk if any upload (and therefore a disk 
write) actually took place
-        val haveSubmitted = forceKeysResubmission || keysNotSub
-        val privs = if (haveSubmitted) {
-            logger.info("Keys submitted to the bank, at ${cfg.hostBaseUrl}")
-            loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
-        } else privsMaybe
-        if (privs == null) {
-            logger.error("Could not reload private keys from disk after 
submission")
-            exitProcess(1)
-        }
-        // Really both must be submitted here.
-        if ((!privs.submitted_hia) || (!privs.submitted_ini)) {
-            logger.error("Cannot continue with non-submitted client keys.")
-            exitProcess(1)
-        }
-        // Eject PDF if the keys were submitted for the first time, or the 
user asked.
-        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?
-            val areKeysOnDisk = runBlocking {
-                doKeysRequestAndUpdateState(
-                    cfg,
-                    privs,
-                    httpClient,
-                    KeysOrderType.HPB
-                )
-            }
-            if (!areKeysOnDisk) {
-                logger.error("Could not download bank keys.  Send client keys 
(and/or related PDF document with --generate-registration-pdf) to the bank.")
-                exitProcess(1)
-            }
-            logger.info("Bank keys stored at ${cfg.bankPublicKeysFilename}")
-        }
-        // bank keys made it to the disk, check if they're accepted.
-        val bankKeysMaybe = loadBankKeys(cfg.bankPublicKeysFilename)
-        if (bankKeysMaybe == null) {
-            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)
-            }
-
-            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.
-
-        // Downloading the list of owned bank account(s).
-        val bankAccounts = runBlocking {
-            fetchBankAccounts(cfg, privs, bankKeys, httpClient)
-        }
-        if (bankAccounts == null) {
-            logger.error("Could not obtain the list of bank accounts from the 
bank.")
-            exitProcess(1)
-        }
-        logger.info("Subscriber's bank accounts fetched.")
-        // 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 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)
-        }
-        println("setup ready")
-    }
-}
-
 /**
  * Main CLI class that collects all the subcommands.
  */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]