gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (c868441a -> 96bbb557)


From: gnunet
Subject: [libeufin] branch master updated (c868441a -> 96bbb557)
Date: Tue, 29 Nov 2022 19:44:42 +0100

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

ms pushed a change to branch master
in repository libeufin.

    from c868441a logging wire transfer subjects along Pain communication
     new 8c26eab2 Check subscriber has rights over debtor IBAN
     new 96bbb557 Avoid retrying invalid Pain.001.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  2 ++
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt | 13 +++++++---
 .../tech/libeufin/nexus/ebics/EbicsClient.kt       |  3 +--
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 27 +++++++++++++-------
 .../tech/libeufin/nexus/iso20022/Iso20022.kt       |  6 ++---
 nexus/src/test/kotlin/DownloadAndSubmit.kt         |  9 +++----
 nexus/src/test/kotlin/MakeEnv.kt                   | 29 +++++++++++++++++++---
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  | 25 +++++++++++++++----
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  |  2 +-
 util/src/main/kotlin/Ebics.kt                      |  1 +
 util/src/main/kotlin/XMLUtil.kt                    |  6 ++++-
 11 files changed, 90 insertions(+), 33 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index d2a18041..30192362 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -211,6 +211,7 @@ object PaymentInitiationsTable : LongIdTable() {
     val creditorBic = text("creditorBic").nullable()
     val creditorName = text("creditorName")
     val submitted = bool("submitted").default(false)
+    var invalid = bool("invalid").nullable()
     val messageId = text("messageId")
 
     /**
@@ -234,6 +235,7 @@ class PaymentInitiationEntity(id: EntityID<Long>) : 
LongEntity(id) {
     var creditorBic by PaymentInitiationsTable.creditorBic
     var creditorName by PaymentInitiationsTable.creditorName
     var submitted by PaymentInitiationsTable.submitted
+    var invalid by PaymentInitiationsTable.invalid
     var paymentInformationId by PaymentInitiationsTable.paymentInformationId
     var instructionId by PaymentInitiationsTable.instructionId
     var messageId by PaymentInitiationsTable.messageId
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
index 10576e30..c6544d5c 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -84,21 +84,28 @@ suspend fun submitAllPaymentInitiations(httpClient: 
HttpClient, accountid: Strin
             HttpStatusCode.NotFound,
             "account not found"
         )
+        /**
+         * Skip submitted and invalid preparations.
+         */
         PaymentInitiationEntity.find {
-            (PaymentInitiationsTable.submitted eq false) and (
-                    PaymentInitiationsTable.bankAccount eq account.id)
+            // Not submitted.
+            (PaymentInitiationsTable.submitted eq false) and
+                    // From the correct bank account.
+            (PaymentInitiationsTable.bankAccount eq account.id)
         }.forEach {
-            // Filter out non EBICS.
+            if (it.invalid == true) return@forEach
             val defaultBankConnectionId = 
it.bankAccount.defaultBankConnection?.id ?: throw NexusError(
                 HttpStatusCode.NotFound,
                 "Default bank connection not found.  Can't submit Pain 
document"
             )
+            // Rare, but filter out bank accounts without a bank connection.
             val bankConnection = 
NexusBankConnectionEntity.findById(defaultBankConnectionId) ?: throw NexusError(
                 HttpStatusCode.InternalServerError,
                 "Bank connection '$defaultBankConnectionId' " +
                         "(pointed by bank account 
'${it.bankAccount.bankAccountName}')" +
                         " not found in the database."
             )
+            // Filter out non EBICS.
             if (bankConnection.type != "ebics") {
                 logger.info("Skipping non-implemented bank connection 
'${bankConnection.type}'")
                 return@forEach
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
index 5516e88c..4a7a7c28 100644
--- 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
index ab359a16..a232bfd1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -44,8 +44,10 @@ import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.insert
 import org.jetbrains.exposed.sql.select
 import org.jetbrains.exposed.sql.statements.api.ExposedBlob
+import org.jetbrains.exposed.sql.transactions.TransactionManager
 import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.nexus.*
+import tech.libeufin.nexus.bankaccount.getPaymentInitiation
 import tech.libeufin.nexus.iso20022.NexusPaymentInitiationData
 import tech.libeufin.nexus.iso20022.createPain001document
 import tech.libeufin.nexus.logger
@@ -180,7 +182,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")
@@ -531,9 +533,18 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol {
             )
             logger.debug("Sending Pain.001: 
${paymentInitiation.paymentInformationId}," +
                     " for payment: '${paymentInitiation.subject}'")
-            if (!XMLUtil.validateFromString(painMessage)) throw NexusError(
-                HttpStatusCode.InternalServerError, "Pain.001 message is 
invalid."
-            )
+            if (!XMLUtil.validateFromString(painMessage)) {
+                logger.error("Pain.001 
${paymentInitiation.paymentInformationId}" +
+                        " is invalid, not submitting it and flag as invalid.")
+                val payment = getPaymentInitiation(paymentInitiationId)
+                payment.invalid = true
+                // The following commit prevents the thrown error
+                // to lose the database transaction data.
+                TransactionManager.current().commit()
+                throw NexusError(
+                    HttpStatusCode.InternalServerError, "Pain.001 message is 
invalid."
+                )
+            }
             object {
                 val subscriberDetails = subscriberDetails
                 val painMessage = painMessage
@@ -546,12 +557,10 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol 
{
             r.painMessage.toByteArray(Charsets.UTF_8),
             EbicsStandardOrderParams()
         )
-        // Mark the payment as submitted.
         transaction {
-            val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
-                ?: throw NexusError(HttpStatusCode.NotFound, "payment 
initiation not found")
-            paymentInitiation.submitted = true
-            paymentInitiation.submissionDate = LocalDateTime.now().millis()
+            val payment = getPaymentInitiation(paymentInitiationId)
+            payment.submitted = true
+            payment.submissionDate = LocalDateTime.now().millis()
         }
     }
 
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
index 592b7aa1..12045f50 100644
--- 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
index f061b4f9..243ae0a8 100644
--- 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.
@@ -93,7 +92,7 @@ fun getCustomEbicsServer(r: EbicsResponses, endpoint: String 
= "/ebicsweb"): App
  * are now 'private'.
  */
 // @Ignore
-class SchedulingTest {
+class DownloadAndSubmit {
     /**
      * Instruct the server to return invalid CAMT content.
      */
@@ -123,7 +122,7 @@ class SchedulingTest {
                             level = FetchLevel.REPORT,
                             "foo"
                         ),
-                        "mock-bank-account"
+                        "foo"
                     )
                 }
             }
@@ -148,7 +147,7 @@ class SchedulingTest {
                         ),
                         transaction {
                             NexusBankAccountEntity.findByName(
-                                "mock-bank-account"
+                                "foo"
                             ) ?: throw Exception("Test failed")
                         }
                     )
diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt
index 5161650e..b4a11254 100644
--- 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),
@@ -85,13 +86,21 @@ fun prepNexusDb() {
             bankAuthenticationPublicKey = 
ExposedBlob(bankKeys.auth.public.encoded)
         }
         val a = NexusBankAccountEntity.new {
-            bankAccountName = "mock-bank-account"
-            iban = USER_IBAN
+            bankAccountName = "foo"
+            iban = FOO_USER_IBAN
             bankCode = "SANDBOXX"
             defaultBankConnection = c
             highestSeenBankMessageSerialId = 0
             accountHolder = "foo"
         }
+        val b = NexusBankAccountEntity.new {
+            bankAccountName = "bar"
+            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
index 5c525d92..19aa31fb 100644
--- 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
index fcc4dfc6..a8ddcf68 100644
--- 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
index ae13c72e..8b9070f0 100644
--- 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 {
diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt
index 1fa993dc..c20a969a 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/util/src/main/kotlin/XMLUtil.kt
@@ -252,7 +252,11 @@ class XMLUtil private constructor() {
             try {
                 getEbicsValidator().validate(xmlDoc)
             } catch (e: Exception) {
-                e.printStackTrace()
+                /**
+                 * Would be convenient to return also the error
+                 * message to the caller, so that it can link it
+                 * to a document ID in the logs.
+                 */
                 logger.warn("Validation failed: ${e}")
                 return false
             }

-- 
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]