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:
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 {