diff options
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 140 |
1 files changed, 68 insertions, 72 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt index ce10dcc6..aae917ba 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -352,24 +352,40 @@ fun preparePrivateKeys(location: String): ClientPrivateKeysFile? { * to send to the bank. */ enum class KeysOrderType { - INI, HIA, HPB + INI, + HIA, + HPB } /** - * Parses the HPB response and stores the bank keys to disk. According to - * the --auto-accept-keys flag marks them as accepted. + * 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("Bank has the following keys, type 'yes, accept' to accept it") + println("Encryption key: $encHash") + println("Authentication key: $authHash") + 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. - * @param autoAccept whether the user gave the --auto-accept-keys CLI flag. - * @return true if the keys were marked as accepted AND the bank keys file - * could be written to the disk. False if even one of the previous - * condition isn't satisfied. + * @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, - autoAccept: Boolean + bankKeys: EbicsKeyManagementResponseContent ): Boolean { val hpbBytes = bankKeys.orderData // silences compiler. if (hpbBytes == null) { @@ -395,29 +411,16 @@ private fun handleHpbResponse( logger.error("Could not import bank authentication key from HPB response, detail: ${e.message}") return false } - - var areKeysAccepted = true - if (!autoAccept) { // asks user. - val encHash = CryptoUtil.getEbicsPublicKeyHash(encPub).toHexString() - val authHash = CryptoUtil.getEbicsPublicKeyHash(authPub).toHexString() - println("Bank has the following keys, type 'yes, accept' to accept it") - println("Encryption key: $encHash") - println("Authentication key: $authHash") - val userResponse: String? = readlnOrNull() - if (userResponse != "yes, accept") { - areKeysAccepted = false - } - } val json = BankPublicKeysFile( bank_authentication_public_key = authPub, bank_encryption_public_key = encPub, - accepted = areKeysAccepted + accepted = false ) if (!syncJsonToDisk(json, cfg.bankPublicKeysFilename)) { logger.error("Failed to persist the bank keys to disk at: ${cfg.bankPublicKeysFilename}") return false } - return areKeysAccepted + return true } /** @@ -438,8 +441,7 @@ suspend fun doKeysRequestAndUpdateState( cfg: EbicsSetupConfig, privs: ClientPrivateKeysFile, client: HttpClient, - orderType: KeysOrderType, - autoAcceptBankKeys: Boolean? = null + orderType: KeysOrderType ): Boolean { val req = when(orderType) { KeysOrderType.INI -> generateIniMessage(cfg, privs) @@ -468,13 +470,7 @@ suspend fun doKeysRequestAndUpdateState( when(orderType) { KeysOrderType.INI -> privs.submitted_ini = true KeysOrderType.HIA -> privs.submitted_hia = true - KeysOrderType.HPB -> { - if (autoAcceptBankKeys == null) { - logger.error("Cannot do HPB without any auto accept policy.") - return false - } - return handleHpbResponse(cfg, ebics, autoAcceptBankKeys) - } + KeysOrderType.HPB -> return handleHpbResponse(cfg, ebics) } if (!syncJsonToDisk(privs, cfg.clientPrivateKeysFilename)) { logger.error("Could not update the ${orderType.name} state on disk") @@ -604,27 +600,12 @@ class EbicsSetup: CliktCommand() { 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. - * - * First, it loads the configuration and checks its correctness. It proceeds - * by creating the private keys (if they weren't found) and by uploading them - * to the bank, if they weren't before (or the --force-keys-resubmission flag - * was given). After the successful upload, it generates a PDF with the key - * fingerprint to let the user send it via post to the bank. - * - * It goes by downloading the bank keys (if they weren't before), and - * by showing their fingerprint to the user. After the user confirms - * them (or the --auto-accept-keys was given), the bank keys are marked - * as accepted. - * - * Finally, we check that the bank hosts the bank account that is - * set in the configuration value ACCOUNT_NUMBER. In such case, we - * update the ACCOUNT_META_DATA file, otherwise we fail by showing - * which bank account was offered by the bank. - * - * Ends with a "setup ready" message. */ override fun run() { val cfg = extractConfig(this.configFile) @@ -638,8 +619,7 @@ class EbicsSetup: CliktCommand() { exitProcess(1) } val httpClient = HttpClient() - // Privs exist. Upload their pubs if forced, or they weren't uploaded yet. - // Whenever one branch fails, the process fails too (doKeysRequest logs the reason). + // Privs exist. Upload their pubs val keysNotSub = !privsMaybe.submitted_ini || !privsMaybe.submitted_hia runBlocking { if ((!privsMaybe.submitted_ini) || forceKeysResubmission) @@ -647,7 +627,7 @@ class EbicsSetup: CliktCommand() { if ((!privsMaybe.submitted_hia) || forceKeysResubmission) doKeysRequestAndUpdateState(cfg, privsMaybe, httpClient, KeysOrderType.HIA).apply { if (!this) exitProcess(1) } } - // Reloading from disk if any upload actually took place + // Reloading new state from disk if any upload actually took place val haveSubmitted = forceKeysResubmission || keysNotSub val privs = if (haveSubmitted) loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename) @@ -663,30 +643,37 @@ class EbicsSetup: CliktCommand() { } // Eject PDF if the keys were submitted for the first time, or the user asked. if (keysNotSub || generateRegistrationPdf) makePdf(privs, cfg) - - // Downloading the bank keys: always fails before the PDF arrives to the bank. - // Note: this step also asks the user about accepting the keys (according to the - // auto-accept flag.) - runBlocking { - if (!doKeysRequestAndUpdateState( - cfg, - privs, - httpClient, - KeysOrderType.HPB, - autoAcceptBankKeys = autoAcceptKeys - )) { - logger.error("Could not download or update the bank keys on disk.") + // Checking if the bank keys exist on disk. + val bankKeysFile = File(cfg.bankPublicKeysFilename) + if (!bankKeysFile.exists()) { + 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) } } + // bank keys made it to the disk, check if they're accepted. val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) if (bankKeys == null) { - logger.error("Could not load the bank keys file from: ${cfg.bankPublicKeysFilename}") + logger.error("Although previous checks, could not load the bank keys file from: ${cfg.bankPublicKeysFilename}") exitProcess(1) } if (!bankKeys.accepted) { - logger.error("Cannot fetch bank accounts: bank keys are not accepted yet.") - exitProcess(1) + + if (autoAcceptKeys) bankKeys.accepted = true + else bankKeys.accepted = askUserToAcceptKeys(bankKeys) + + if (bankKeys.accepted && !syncJsonToDisk(bankKeys, cfg.bankPublicKeysFilename)) { + logger.error("Could not set bank keys as accepted on disk.") + exitProcess(1) + } } // Downloading the list of owned bank account(s). val bankAccounts = runBlocking { @@ -709,13 +696,22 @@ class EbicsSetup: CliktCommand() { 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_iban = foundIban ?: cfg.accountNumber, // stick to config, if bank was silent. account_holder_name = bankAccounts.userInfo.name ?: "Account holder name not given", - bank_code = foundBic // null if bank was silent, also not mandatory to operate. + 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, it remains null.") + return@bic null } ) if (!syncJsonToDisk(accountMetaData, cfg.bankAccountMetadataFilename)) { logger.error("Failed to persist bank account meta-data at: ${cfg.bankAccountMetadataFilename}") |