commit d727095c1bdffc9700ca657bc8bd88af44929949
parent 1877afa8413fed57f4cbe2c63920a1104c596898
Author: Antoine A <>
Date: Mon, 14 Oct 2024 15:03:12 +0200
bank: check withdrawal ownership on abort in core bank API
Diffstat:
6 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt
@@ -119,7 +119,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) {
}
post("/taler-integration/withdrawal-operation/{wopid}/abort") {
val uuid = call.uuidPath("wopid")
- when (db.withdrawal.abort(uuid)) {
+ when (db.withdrawal.abort(uuid, null)) {
AbortResult.UnknownOperation -> throw notFound(
"Withdrawal operation '$uuid' not found",
TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt
@@ -607,7 +607,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/abort") {
val opId = call.uuidPath("withdrawal_id")
- when (db.withdrawal.abort(opId)) {
+ when (db.withdrawal.abort(opId, call.username)) {
AbortResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
@@ -100,15 +100,19 @@ class WithdrawalDAO(private val db: Database) {
}
/** Abort withdrawal operation [uuid] */
- suspend fun abort(uuid: UUID): AbortResult = db.serializable(
+ suspend fun abort(
+ uuid: UUID,
+ username: String?,
+ ): AbortResult = db.serializable(
"""
SELECT
out_no_op,
out_already_confirmed
- FROM abort_taler_withdrawal(?)
+ FROM abort_taler_withdrawal(?, ?)
"""
) {
setObject(1, uuid)
+ setString(2, username)
one {
when {
it.getBoolean("out_no_op") -> AbortResult.UnknownOperation
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -1397,6 +1397,70 @@ class CoreBankWithdrawalApiTest {
.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
}
+ // POST /accounts/USERNAME/withdrawals/withdrawal_id/abort
+ @Test
+ fun abort() = bankSetup {
+ authRoutine(HttpMethod.Post, "/accounts/merchant/withdrawals/42/abort")
+
+ // Check abort created
+ client.postA("/accounts/merchant/withdrawals") {
+ json { "amount" to "KUDOS:1" }
+ }.assertOkJson<BankAccountCreateWithdrawalResponse> {
+ val uuid = it.withdrawal_id
+
+ // Check OK
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort").assertNoContent()
+ // Check idempotence
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort").assertNoContent()
+ }
+
+ // Check abort selected
+ client.postA("/accounts/merchant/withdrawals") {
+ json { "amount" to "KUDOS:1" }
+ }.assertOkJson<BankAccountCreateWithdrawalResponse> {
+ val uuid = it.withdrawal_id
+ withdrawalSelect(uuid)
+
+ // Check OK
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort").assertNoContent()
+ // Check idempotence
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort").assertNoContent()
+ }
+
+ // Check abort confirmed
+ client.postA("/accounts/merchant/withdrawals") {
+ json { "amount" to "KUDOS:1" }
+ }.assertOkJson<BankAccountCreateWithdrawalResponse> {
+ val uuid = it.withdrawal_id
+ withdrawalSelect(uuid)
+ client.postA("/accounts/merchant/withdrawals/$uuid/confirm").assertNoContent()
+
+ // Check error
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort")
+ .assertConflict(TalerErrorCode.BANK_ABORT_CONFIRM_CONFLICT)
+ }
+
+ // Check confirm another user's operation
+ client.postA("/accounts/customer/withdrawals") {
+ json { "amount" to "KUDOS:1" }
+ }.assertOkJson<BankAccountCreateWithdrawalResponse> {
+ val uuid = it.withdrawal_id
+ withdrawalSelect(uuid)
+
+ // Check error
+ client.postA("/accounts/merchant/withdrawals/$uuid/abort")
+ .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
+ }
+
+ // Check bad UUID
+ client.postA("/accounts/merchant/withdrawals/chocolate/abort")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+
+ // Check unknown
+ client.postA("/accounts/merchant/withdrawals/${UUID.randomUUID()}/abort")
+ .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
+ }
+
// POST /accounts/USERNAME/withdrawals/withdrawal_id/confirm
@Test
fun confirm() = bankSetup {
diff --git a/bank/src/test/kotlin/bench.kt b/bank/src/test/kotlin/bench.kt
@@ -257,7 +257,7 @@ class Bench {
"amount" to "KUDOS:0.0001"
}
}.assertOkJson<BankAccountCreateWithdrawalResponse>().withdrawal_id
- client.post("/taler-integration/withdrawal-operation/$uuid/abort")
+ client.postA("/accounts/customer/withdrawals/$uuid/abort")
.assertNoContent()
}
diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql
@@ -1048,6 +1048,7 @@ COMMENT ON FUNCTION select_taler_withdrawal IS 'Set details of a withdrawal oper
CREATE FUNCTION abort_taler_withdrawal(
IN in_withdrawal_uuid uuid,
+ IN in_username TEXT,
OUT out_no_op BOOLEAN,
OUT out_already_confirmed BOOLEAN
)
@@ -1055,7 +1056,11 @@ LANGUAGE plpgsql AS $$
BEGIN
UPDATE taler_withdrawal_operations
SET aborted = NOT confirmation_done
+ FROM bank_accounts
+ JOIN customers ON owning_customer_id=customer_id
WHERE withdrawal_uuid=in_withdrawal_uuid
+ AND wallet_bank_account=bank_account_id
+ AND (in_username IS NULL OR username = in_username)
RETURNING confirmation_done
INTO out_already_confirmed;
IF NOT FOUND OR out_already_confirmed THEN