libeufin

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

commit 8c26eab27768ae507a5460770ace216fd43f882c
parent c868441acbfb1f3e72f5a254e97c38883afaca79
Author: MS <ms@taler.net>
Date:   Tue, 29 Nov 2022 18:32:14 +0100

Check subscriber has rights over debtor IBAN

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt | 3+--
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt | 6+++---
Mnexus/src/test/kotlin/DownloadAndSubmit.kt | 7+++----
Mnexus/src/test/kotlin/MakeEnv.kt | 27++++++++++++++++++++++++---
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 25++++++++++++++++++++-----
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 2+-
Mutil/src/main/kotlin/Ebics.kt | 1+
8 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt @@ -238,14 +238,13 @@ suspend fun doEbicsUploadTransaction( subscriberDetails.ebicsUrl, payload ) - val txResp = parseAndValidateEbicsResponse(subscriberDetails, txRespStr) when (txResp.technicalReturnCode) { EbicsReturnCode.EBICS_OK -> { } else -> { throw NexusError(HttpStatusCode.InternalServerError, - "Unexpected EBICS return code: ${txResp.technicalReturnCode}" + "Unexpected EBICS technical return code: ${txResp.technicalReturnCode}" ) } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -180,7 +180,7 @@ private fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity) /** * Retrieve Ebics subscriber details given a bank connection. */ -private fun getEbicsSubscriberDetails(bankConnectionId: String): EbicsClientSubscriberDetails { +fun getEbicsSubscriberDetails(bankConnectionId: String): EbicsClientSubscriberDetails { val transport = NexusBankConnectionEntity.findByName(bankConnectionId) if (transport == null) { throw NexusError(HttpStatusCode.NotFound, "transport not found") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt @@ -343,15 +343,15 @@ data class NexusPaymentInitiationData( val debtorName: String, val messageId: String, val paymentInformationId: String, - val endToEndId: String?, + val endToEndId: String? = null, val amount: String, val currency: String, val subject: String, val preparationTimestamp: Long, val creditorName: String, val creditorIban: String, - val creditorBic: String?, - val instructionId: String? + val creditorBic: String? = null, + val instructionId: String? = null ) /** diff --git a/nexus/src/test/kotlin/DownloadAndSubmit.kt b/nexus/src/test/kotlin/DownloadAndSubmit.kt @@ -30,9 +30,8 @@ import tech.libeufin.util.ebics_h004.EbicsTypes * This source is NOT a test case -- as it uses no assertions -- * but merely a tool to download and submit payments to the bank * via Nexus. - * / - */ + /** * Data to make the test server return for EBICS * phases. Currently only init is supported. @@ -92,8 +91,8 @@ fun getCustomEbicsServer(r: EbicsResponses, endpoint: String = "/ebicsweb"): App * and having had access to runTask and TaskSchedule, that * are now 'private'. */ -// @Ignore -class SchedulingTest { +@Ignore +class DownloadAndSubmit { /** * Instruct the server to return invalid CAMT content. */ diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt @@ -18,7 +18,8 @@ data class EbicsKeys( const val TEST_DB_FILE = "/tmp/nexus-test.sqlite3" const val TEST_DB_CONN = "jdbc:sqlite:$TEST_DB_FILE" val BANK_IBAN = getIban() -val USER_IBAN = getIban() +val FOO_USER_IBAN = getIban() +val BAR_USER_IBAN = getIban() val bankKeys = EbicsKeys( auth = CryptoUtil.generateRsaKeyPair(2048), @@ -86,12 +87,20 @@ fun prepNexusDb() { } val a = NexusBankAccountEntity.new { bankAccountName = "mock-bank-account" - iban = USER_IBAN + iban = FOO_USER_IBAN bankCode = "SANDBOXX" defaultBankConnection = c highestSeenBankMessageSerialId = 0 accountHolder = "foo" } + val b = NexusBankAccountEntity.new { + bankAccountName = "bar-bank-account" + iban = BAR_USER_IBAN + bankCode = "SANDBOXX" + defaultBankConnection = c + highestSeenBankMessageSerialId = 0 + accountHolder = "bar" + } } } @@ -122,7 +131,7 @@ fun prepSandboxDb() { this.signaturePrivateKey = ExposedBlob(bankKeys.sig.private.encoded) } val bankAccount = BankAccountEntity.new { - iban = USER_IBAN + iban = FOO_USER_IBAN /** * For now, keep same semantics of Pybank: a username * is AS WELL a bank account label. In other words, it @@ -133,6 +142,18 @@ fun prepSandboxDb() { this.demoBank = demoBank isPublic = false } + val otherBankAccount = BankAccountEntity.new { + iban = BAR_USER_IBAN + /** + * For now, keep same semantics of Pybank: a username + * is AS WELL a bank account label. In other words, it + * identifies a customer AND a bank account. + */ + label = "bar" + owner = "bar" + this.demoBank = demoBank + isPublic = false + } tech.libeufin.sandbox.EbicsSubscriberEntity.new { hostId = "eufinSandbox" partnerId = "foo" diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -81,8 +81,8 @@ class EbicsInvalidRequestError : EbicsRequestError( "[EBICS_INVALID_REQUEST] Invalid request", "060102" ) -class EbicsAccountAuthorisationFailed : EbicsRequestError( - "[EBICS_ACCOUNT_AUTHORISATION_FAILED] Subscriber's signature didn't verify", +class EbicsAccountAuthorisationFailed(reason: String) : EbicsRequestError( + "[EBICS_ACCOUNT_AUTHORISATION_FAILED] $reason", "091302" ) @@ -702,11 +702,23 @@ private fun parsePain001(paymentRequest: String): PainParseResult { /** * Process a payment request in the pain.001 format. */ -private fun handleCct(paymentRequest: String) { +private fun handleCct(paymentRequest: String, + requestingSubscriber: EbicsSubscriberEntity +) { val parseResult = parsePain001(paymentRequest) logger.debug("Handling Pain.001: ${parseResult.pmtInfId}, " + "for payment: ${parseResult.subject}") transaction(Connection.TRANSACTION_SERIALIZABLE, repetitionAttempts = 10) { + // Check that subscriber has a bank account + // and that they have rights over the debtor IBAN + if (requestingSubscriber.bankAccount == null) throw EbicsProcessingError( + "Subscriber '${requestingSubscriber.userId}' does not have a bank account." + ) + if (requestingSubscriber.bankAccount!!.iban != parseResult.debtorIban) throw + EbicsAccountAuthorisationFailed( + "Subscriber '${requestingSubscriber.userId}' does not have rights" + + " over the debtor IBAN '${parseResult.debtorIban}'" + ) val maybeExist = BankAccountTransactionEntity.find { BankAccountTransactionsTable.pmtInfId eq parseResult.pmtInfId }.firstOrNull() @@ -722,6 +734,7 @@ private fun handleCct(paymentRequest: String) { "[EBICS_PROCESSING_ERROR] Currency (${parseResult.currency}) not supported.", "091116" ) + // FIXME: check that debtor IBAN _is_ the requesting subscriber. BankAccountTransactionEntity.new { account = bankAccount demobank = bankAccount.demoBank @@ -1278,7 +1291,9 @@ private fun handleEbicsUploadTransactionTransmission(requestContext: RequestCont } } if (getOrderTypeFromTransactionId(requestTransactionID) == "CCT") { - handleCct(unzippedData.toString(Charsets.UTF_8)) + handleCct(unzippedData.toString(Charsets.UTF_8), + requestContext.subscriber + ) } return EbicsResponse.createForUploadTransferPhase( requestTransactionID, @@ -1403,7 +1418,7 @@ suspend fun ApplicationCall.ebicsweb() { // Step 2 of 3: Validate the signature val verifyResult = XMLUtil.verifyEbicsDocument(requestDocument, requestContext.clientAuthPub) if (!verifyResult) { - throw EbicsAccountAuthorisationFailed() + throw EbicsAccountAuthorisationFailed("Subscriber's signature did not verify") } // Step 3 of 3: Generate response val ebicsResponse: EbicsResponse = when (requestObject.header.mutable.transactionPhase) { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -539,7 +539,7 @@ val sandboxApp: Application.() -> Unit = { ) } exception<EbicsRequestError> { e -> - logger.debug("Handling EbicsRequestError: $e") + logger.info("Handling EbicsRequestError: ${e.message}") respondEbicsTransfer(call, e.errorText, e.errorCode) } exception<Throwable> { cause -> diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt @@ -365,6 +365,7 @@ enum class EbicsReturnCode(val errorCode: String) { EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"), EBICS_INVALID_USER_OR_USER_STATE("091002"), EBICS_PROCESSING_ERROR("091116"), + EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"), EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"); companion object {