libeufin

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

commit 27ac4e561fddd4f59f84bf06642a1d3f8eb11d6b
parent 117fa34b78db0af0dcbcd281d17b7c0e0a5294cc
Author: Antoine A <>
Date:   Mon, 11 Dec 2023 15:06:23 +0000

Restrict contact data patch to admin

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 4++++
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 3++-
Mbank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 13+++++++++++++
Mbank/src/test/kotlin/CoreBankApiTest.kt | 31++++++++++++++++++++++---------
Mbank/src/test/kotlin/helpers.kt | 3++-
Mutil/src/main/kotlin/TalerErrorCode.kt | 8++++++++
6 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -272,6 +272,10 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { "non-admin user cannot change their debt limit", TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT ) + AccountPatchResult.NonAdminContact -> throw conflict( + "non-admin user cannot change their contact info", + TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT + ) } } patch("/accounts/{USERNAME}/auth") { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -392,7 +392,8 @@ class EditAccount : CliktCommand( throw Exception("Account '$username' not found") AccountPatchResult.NonAdminName, AccountPatchResult.NonAdminCashout, - AccountPatchResult.NonAdminDebtLimit -> { + AccountPatchResult.NonAdminDebtLimit, + AccountPatchResult.NonAdminContact -> { // Unreachable as we edit account as admin } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -199,6 +199,7 @@ class AccountDAO(private val db: Database) { NonAdminName, NonAdminCashout, NonAdminDebtLimit, + NonAdminContact, Success } @@ -218,6 +219,8 @@ class AccountDAO(private val db: Database) { val checkName = !isAdmin && !allowEditName && name != null val checkCashout = !isAdmin && !allowEditCashout && cashoutPayto.isSome() val checkDebtLimit = !isAdmin && debtLimit != null + val checkPhone = !isAdmin && phone.isSome() + val checkEmail = !isAdmin && email.isSome() // Get user ID and check reconfig rights val customer_id = conn.prepareStatement(""" @@ -226,6 +229,8 @@ class AccountDAO(private val db: Database) { ,(${ if (checkName) "name != ? " else "false" }) as name_change ,(${ if (checkCashout) "cashout_payto IS DISTINCT FROM ?" else "false" }) as cashout_change ,(${ if (checkDebtLimit) "max_debt != (?, ?)::taler_amount" else "false" }) as debt_limit_change + ,(${ if (checkPhone) "phone IS DISTINCT FROM ?" else "false" }) as phone_change + ,(${ if (checkEmail) "email IS DISTINCT FROM ?" else "false" }) as email_change FROM customers JOIN bank_accounts ON customer_id=owning_customer_id @@ -242,6 +247,12 @@ class AccountDAO(private val db: Database) { setLong(idx, debtLimit!!.value); idx++ setInt(idx, debtLimit.frac); idx++ } + if (checkPhone) { + setString(idx, phone.get()); idx++ + } + if (checkEmail) { + setString(idx, email.get()); idx++ + } setString(idx, login) executeQuery().use { when { @@ -249,6 +260,8 @@ class AccountDAO(private val db: Database) { it.getBoolean("name_change") -> return@transaction AccountPatchResult.NonAdminName it.getBoolean("cashout_change") -> return@transaction AccountPatchResult.NonAdminCashout it.getBoolean("debt_limit_change") -> return@transaction AccountPatchResult.NonAdminDebtLimit + it.getBoolean("phone_change") -> return@transaction AccountPatchResult.NonAdminContact + it.getBoolean("email_change") -> return@transaction AccountPatchResult.NonAdminContact else -> it.getLong("customer_id") } } diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -376,16 +376,23 @@ class CoreBankAccountsApiTest { client.deleteA("/accounts/exchange").assertNoContent() } - suspend fun ApplicationTestBuilder.checkAdminOnly(req: JsonElement, error: TalerErrorCode) { - // Checking ordinary user doesn't get to patch + suspend fun ApplicationTestBuilder.checkAdminOnly( + req: JsonElement, + error: TalerErrorCode + ) { + // Check restricted client.patchA("/accounts/merchant") { json(req) }.assertConflict(error) - // Finally checking that admin does get to patch + // Check admin always can client.patch("/accounts/merchant") { pwAuth("admin") json(req) }.assertNoContent() + // Check idempotent + client.patchA("/accounts/merchant") { + json(req) + }.assertNoContent() } // PATCH /accounts/USERNAME @@ -397,10 +404,6 @@ class CoreBankAccountsApiTest { val cashout = IbanPayTo(genIbanPaytoUri()) val req = obj { "cashout_payto_uri" to cashout.canonical - "contact_data" to obj { - "phone" to "+99" - "email" to "foo@example.com" - } "name" to "Roger" "is_public" to true } @@ -416,6 +419,14 @@ class CoreBankAccountsApiTest { obj(req) { "debit_threshold" to "KUDOS:100" }, TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT ) + checkAdminOnly( + obj(req) { "contact_data" to obj { "phone" to "+99" } }, + TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT + ) + checkAdminOnly( + obj(req) { "contact_data" to obj { "email" to "foo@example.com" } }, + TalerErrorCode.BANK_NON_ADMIN_PATCH_CONTACT + ) // Check currency client.patch("/accounts/merchant") { @@ -975,7 +986,8 @@ class CoreBankCashoutApiTest { client.postA("/accounts/customer/cashouts") { json(req) }.assertConflict(TalerErrorCode.BANK_MISSING_TAN_INFO) - client.patchA("/accounts/customer") { + client.patch("/accounts/customer") { + pwAuth("admin") json { "contact_data" to obj { "phone" to "+99" @@ -1132,7 +1144,8 @@ class CoreBankCashoutApiTest { fun confirm() = bankSetup { _ -> authRoutine(HttpMethod.Post, "/accounts/merchant/cashouts/42/confirm") - client.patchA("/accounts/customer") { + client.patch("/accounts/customer") { + pwAuth("admin") json { "contact_data" to obj { "phone" to "+99" diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -235,7 +235,8 @@ suspend fun ApplicationTestBuilder.withdrawal(amount: String) { } suspend fun ApplicationTestBuilder.fillCashoutInfo(account: String) { - client.patchA("/accounts/$account") { + client.patch("/accounts/$account") { + pwAuth("admin") json { "cashout_payto_uri" to unknownPayto "contact_data" to obj { diff --git a/util/src/main/kotlin/TalerErrorCode.kt b/util/src/main/kotlin/TalerErrorCode.kt @@ -3482,6 +3482,14 @@ enum class TalerErrorCode(val code: Int) { /** + * A non-admin user has tried to change their contact info. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_NON_ADMIN_PATCH_CONTACT(5141), + + + /** * The sync service failed find the account in its database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side).