libeufin

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

commit 6ec6d539216c2deb1afc0aec0b845c0267c1e23b
parent 8536282bb6046b1cb6c4f18461d96543dc77f979
Author: Antoine A <>
Date:   Wed,  1 Nov 2023 11:07:58 +0000

Improve account creation and patching

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Authentication.kt | 10+++++++++-
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 11++++++-----
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 2+-
Mbank/src/test/kotlin/CoreBankApiTest.kt | 29++++++++++++++++++++++++++---
4 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt @@ -41,6 +41,14 @@ fun Route.authAdmin(db: Database, scope: TokenScope, enforce: Boolean = true, ca if (login != "admin") { throw unauthorized("Only administrator allowed") } + context.attributes.put(AUTH_IS_ADMIN, true) + } else { + val login = try { + context.authenticateBankRequest(db, scope) + } catch (e: Exception) { + null + } + context.attributes.put(AUTH_IS_ADMIN, login == "admin") } } @@ -65,7 +73,7 @@ fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, req val PipelineContext<Unit, ApplicationCall>.username: String get() = call.username val PipelineContext<Unit, ApplicationCall>.isAdmin: Boolean get() = call.isAdmin val ApplicationCall.username: String get() = expectUriComponent("USERNAME") -val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: throw Exception("No auth") +val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: false private fun Route.intercept(callback: Route.() -> Unit, interceptor: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit): Route { val subRoute = createChild(object : RouteSelector() { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -149,6 +149,8 @@ private fun Routing.coreBankAccountsMgmtApi(db: Database, ctx: BankConfig) { "Username '${req.username}' is reserved.", TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT ) + if (req.is_taler_exchange && !isAdmin) + throw forbidden("Only admin can create exchange accounts") val internalPayto = req.internal_payto_uri ?: IbanPayTo(genIbanPaytoUri()) val result = db.accountCreate( @@ -212,13 +214,12 @@ private fun Routing.coreBankAccountsMgmtApi(db: Database, ctx: BankConfig) { } auth(db, TokenScope.readwrite, allowAdmin = true) { patch("/accounts/{USERNAME}") { - // admin is not allowed itself to change its own details. - if (username == "admin") throw forbidden("admin account not patchable") - val req = call.receive<AccountReconfiguration>() req.debit_threshold?.run { ctx.checkInternalCurrency(this) } - if (req.is_exchange != null && !isAdmin) + if (req.is_taler_exchange != null && username == "admin") + throw forbidden("admin account cannot be an exchange") + if (req.is_taler_exchange != null && !isAdmin) throw forbidden("non-admin user cannot change their exchange nature") val res = db.accountReconfig( @@ -226,7 +227,7 @@ private fun Routing.coreBankAccountsMgmtApi(db: Database, ctx: BankConfig) { name = req.name, cashoutPayto = req.cashout_address, emailAddress = req.challenge_contact_data?.email, - isTalerExchange = req.is_exchange, + isTalerExchange = req.is_taler_exchange, phoneNumber = req.challenge_contact_data?.phone, debtLimit = req.debit_threshold, isAdmin = isAdmin diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -621,6 +621,6 @@ data class AccountReconfiguration( val challenge_contact_data: ChallengeContactData?, val cashout_address: IbanPayTo?, val name: String?, - val is_exchange: Boolean?, + val is_taler_exchange: Boolean?, val debit_threshold: TalerAmount? ) \ No newline at end of file diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -206,6 +206,20 @@ class CoreBankAccountsMgmtApiTest { }.assertForbidden().assertErr(TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT) } + // Only admin can create exchange account + val exchangeReq = json(req) { + "username" to "better-exchange" + "internal_payto_uri" to genIbanPaytoUri() + "is_taler_exchange" to true + } + client.post("/accounts") { + jsonBody(exchangeReq) + }.assertForbidden() + client.post("/accounts") { + basicAuth("admin", "admin-password") + jsonBody(exchangeReq) + }.assertCreated() + // Testing login conflict client.post("/accounts") { jsonBody(json(req) { @@ -277,12 +291,10 @@ class CoreBankAccountsMgmtApiTest { "name" to "Mallory" } - // Ordinary user client.post("/accounts") { basicAuth("merchant", "merchant-password") jsonBody(req) }.assertUnauthorized() - // Administrator client.post("/accounts") { basicAuth("admin", "admin-password") jsonBody(req) @@ -378,8 +390,19 @@ class CoreBankAccountsMgmtApiTest { } checkAdminOnly(json(req) { "name" to "Another Foo" }) - checkAdminOnly(json(req) { "is_exchange" to true }) + checkAdminOnly(json(req) { "is_taler_exchange" to true }) checkAdminOnly(json(req) { "debit_threshold" to "KUDOS:100" }) + + // Check admin account cannot be exchange + client.patch("/accounts/admin") { + basicAuth("admin", "admin-password") + jsonBody(json { "is_taler_exchange" to true }) + }.assertForbidden() + // But we can change its debt limit + client.patch("/accounts/admin") { + basicAuth("admin", "admin-password") + jsonBody(json { "debit_threshold" to "KUDOS:100" }) + }.assertNoContent() // Check currency client.patch("/accounts/merchant") {