libeufin

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

commit 1e658ae784f94df9880db31732c539eeb930df4f
parent c31db1a6cb2adc62e0bd7a564c3b90e8ec3ae3f7
Author: Antoine A <>
Date:   Fri,  1 Dec 2023 11:49:28 +0000

Restrict exchange account deletion when conversion is enabled

Diffstat:
Mbank/conf/test_bonus.conf | 1-
Abank/conf/test_no_conversion.conf | 9+++++++++
Mbank/conf/test_restrict.conf | 2++
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 7+++++--
Mbank/src/test/kotlin/ConversionApiTest.kt | 2+-
Mbank/src/test/kotlin/CoreBankApiTest.kt | 47++++++++++++++++++++++++++++++++---------------
Mbank/src/test/kotlin/helpers.kt | 8++++++++
7 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/bank/conf/test_bonus.conf b/bank/conf/test_bonus.conf @@ -1,6 +1,5 @@ [libeufin-bank] CURRENCY = KUDOS -DEFAULT_CUSTOMER_DEBT_LIMIT = KUDOS:0 DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000 REGISTRATION_BONUS = KUDOS:100 ALLOW_REGISTRATION = yes diff --git a/bank/conf/test_no_conversion.conf b/bank/conf/test_no_conversion.conf @@ -0,0 +1,8 @@ +[libeufin-bank] +CURRENCY = KUDOS +DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000 +ALLOW_REGISTRATION = yes +ALLOW_ACCOUNT_DELETION = yes + +[libeufin-bankdb-postgres] +CONFIG = postgresql:///libeufincheck +\ No newline at end of file diff --git a/bank/conf/test_restrict.conf b/bank/conf/test_restrict.conf @@ -2,6 +2,8 @@ CURRENCY = KUDOS DEFAULT_CUSTOMER_DEBT_LIMIT = KUDOS:100 DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000 +allow_conversion = YES +FIAT_CURRENCY = EUR [libeufin-bankdb-postgres] CONFIG = postgresql:///libeufincheck \ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -105,7 +105,6 @@ private fun Routing.coreBankTokenApi(db: Database) { logger.debug("Creating token with days duration: ${tokenDuration.toDays()}") creationTime.plus(tokenDuration) } catch (e: Exception) { - logger.error("Could not add token duration to current time: ${e.message}") throw badRequest("Bad token duration: ${e.message}") } } @@ -187,13 +186,17 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { requireAdmin = !ctx.allowAccountDeletion ) { delete("/accounts/{USERNAME}") { - // TODO prevent delection if exchange account if conversion is enabled // Not deleting reserved names. if (RESERVED_ACCOUNTS.contains(username)) throw conflict( "Cannot delete reserved accounts", TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT ) + if (username == "exchange" && ctx.allowConversion) + throw conflict( + "Cannot delete 'exchange' accounts when conversion is enabled", + TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT + ) when (db.account.delete(username)) { AccountDeletionResult.UnknownAccount -> throw notFound( diff --git a/bank/src/test/kotlin/ConversionApiTest.kt b/bank/src/test/kotlin/ConversionApiTest.kt @@ -124,7 +124,7 @@ class ConversionApiTest { } @Test - fun notImplemented() = bankSetup("test_restrict.conf") { _ -> + fun notImplemented() = bankSetup("test_no_conversion.conf") { _ -> client.get("/conversion-info/cashin-rate") .assertNotImplemented() client.get("/conversion-info/cashout-rate") diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -164,7 +164,7 @@ class CoreBankTokenApiTest { class CoreBankAccountsApiTest { // Testing the account creation and its idempotency @Test - fun createAccountTest() = bankSetup { _ -> + fun create() = bankSetup { _ -> val ibanPayto = genIbanPaytoUri() val req = obj { "username" to "foo" @@ -222,7 +222,7 @@ class CoreBankAccountsApiTest { // Test account created with bonus @Test - fun createAccountBonusTest() = bankSetup(conf = "test_bonus.conf") { _ -> + fun createBonus() = bankSetup(conf = "test_bonus.conf") { _ -> val req = obj { "username" to "foo" "password" to "xyz" @@ -255,7 +255,7 @@ class CoreBankAccountsApiTest { // Test admin-only account creation @Test - fun createAccountRestrictedTest() = bankSetup(conf = "test_restrict.conf") { _ -> + fun createRestricted() = bankSetup(conf = "test_restrict.conf") { _ -> authRoutine(HttpMethod.Post, "/accounts", requireAdmin = true) client.post("/accounts") { pwAuth("admin") @@ -269,7 +269,7 @@ class CoreBankAccountsApiTest { // DELETE /accounts/USERNAME @Test - fun deleteAccount() = bankSetup { _ -> + fun delete() = bankSetup { _ -> // Unknown account client.delete("/accounts/unknown") { pwAuth("admin") @@ -281,6 +281,8 @@ class CoreBankAccountsApiTest { pwAuth("admin") }.assertConflict(TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT) } + client.deleteA("/accounts/exchange") + .assertConflict(TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT) // successful deletion client.post("/accounts") { @@ -301,18 +303,33 @@ class CoreBankAccountsApiTest { // fail to delete, due to a non-zero balance. tx("customer", "KUDOS:1", "merchant") - client.delete("/accounts/merchant") { - pwAuth("admin") - }.assertConflict(TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO) + client.deleteA("/accounts/merchant") + .assertConflict(TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO) tx("merchant", "KUDOS:1", "customer") - client.delete("/accounts/merchant") { + client.deleteA("/accounts/merchant") + .assertNoContent() + } + + // Test admin-only account deletion + @Test + fun deleteRestricted() = bankSetup(conf = "test_restrict.conf") { _ -> + authRoutine(HttpMethod.Post, "/accounts", requireAdmin = true) + // Exchange is still restricted + client.delete("/accounts/exchange") { pwAuth("admin") - }.assertNoContent() + }.assertConflict(TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT) + } + + // Test delete exchange account + @Test + fun deleteNoConversion() = bankSetup(conf = "test_no_conversion.conf") { _ -> + // Exchange is no longer restricted + client.deleteA("/accounts/exchange").assertNoContent() } // PATCH /accounts/USERNAME @Test - fun accountReconfig() = bankSetup { _ -> + fun reconfig() = bankSetup { _ -> authRoutine(HttpMethod.Patch, "/accounts/merchant", withAdmin = true) // Successful attempt now. @@ -383,7 +400,7 @@ class CoreBankAccountsApiTest { // PATCH /accounts/USERNAME/auth @Test - fun passwordChangeTest() = bankSetup { _ -> + fun passwordChange() = bankSetup { _ -> authRoutine(HttpMethod.Patch, "/accounts/merchant/auth", withAdmin = true) // Changing the password. @@ -436,7 +453,7 @@ class CoreBankAccountsApiTest { // GET /public-accounts and GET /accounts @Test - fun accountsListTest() = bankSetup { _ -> + fun list() = bankSetup(conf = "test_no_conversion.conf") { _ -> authRoutine(HttpMethod.Get, "/accounts", requireAdmin = true) // Remove default accounts listOf("merchant", "exchange", "customer").forEach { @@ -498,7 +515,7 @@ class CoreBankAccountsApiTest { // GET /accounts/USERNAME @Test - fun getAccountTest() = bankSetup { _ -> + fun get() = bankSetup { _ -> authRoutine(HttpMethod.Get, "/accounts/merchant", withAdmin = true) // Check ok client.getA("/accounts/merchant").assertOkJson<AccountData> { @@ -510,7 +527,7 @@ class CoreBankAccountsApiTest { class CoreBankTransactionsApiTest { // GET /transactions @Test - fun testHistory() = bankSetup { _ -> + fun history() = bankSetup { _ -> authRoutine(HttpMethod.Get, "/accounts/merchant/transactions") historyRoutine<BankAccountTransactionsResponse>( url = "/accounts/customer/transactions", @@ -1248,7 +1265,7 @@ class CoreBankCashoutApiTest { } @Test - fun notImplemented() = bankSetup("test_restrict.conf") { _ -> + fun notImplemented() = bankSetup("test_no_conversion.conf") { _ -> client.get("/accounts/customer/cashouts") .assertNotImplemented() } diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -375,6 +375,14 @@ inline suspend fun HttpClient.patchA(url: String, builder: HttpRequestBuilder.() } } +/** Auto auth delete request */ +inline suspend fun HttpClient.deleteA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { + return delete(url) { + pwAuth() + builder(this) + } +} + fun HttpRequestBuilder.pwAuth(username: String? = null) { if (username != null) { basicAuth("$username", "$username-password")