gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: pointing demobanks


From: gnunet
Subject: [libeufin] branch master updated: pointing demobanks
Date: Wed, 22 Mar 2023 14:32:55 +0100

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 10bc544c pointing demobanks
10bc544c is described below

commit 10bc544c731291090683bc67e421c1740f6dc269
Author: ms <ms@taler.net>
AuthorDate: Wed Mar 22 14:27:06 2023 +0100

    pointing demobanks
    
    Avoid relying on the default demobank along the
    HTTP handlers, to honor the demobanks multitenancy.
    This change aims to also make the code compatible
    with DD38.  Polishing (Access API) access control
    to bank accounts too.
---
 nexus/src/test/kotlin/SandboxBankAccountTest.kt    |   8 +-
 nexus/src/test/kotlin/SandboxCircuitApiTest.kt     |   1 -
 .../kotlin/tech/libeufin/sandbox/CircuitApi.kt     |  33 +++--
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  |  14 +-
 .../main/kotlin/tech/libeufin/sandbox/Helpers.kt   |  28 +---
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 144 +++++++++++----------
 .../kotlin/tech/libeufin/sandbox/bankAccount.kt    |  35 +++--
 util/src/main/kotlin/HTTP.kt                       |   2 +-
 8 files changed, 147 insertions(+), 118 deletions(-)

diff --git a/nexus/src/test/kotlin/SandboxBankAccountTest.kt 
b/nexus/src/test/kotlin/SandboxBankAccountTest.kt
index 7d6aec9e..799f3435 100644
--- a/nexus/src/test/kotlin/SandboxBankAccountTest.kt
+++ b/nexus/src/test/kotlin/SandboxBankAccountTest.kt
@@ -25,7 +25,7 @@ class SandboxBankAccountTest {
              * the payment is still pending (= not booked), the pending
              * transactions must be included in the calculation.
              */
-            var bankBalance = getBalance("admin", true)
+            var bankBalance = getBalance("admin")
             assert(bankBalance == parseDecimal("-1"))
             wireTransfer(
                 "foo",
@@ -34,7 +34,7 @@ class SandboxBankAccountTest {
                 "Show up in logging!",
                 "TESTKUDOS:5"
             )
-            bankBalance = getBalance("admin", true)
+            bankBalance = getBalance("admin")
             assert(bankBalance == parseDecimal("4"))
             // Trigger Insufficient funds case for users.
             try {
@@ -63,9 +63,9 @@ class SandboxBankAccountTest {
                 assert(e.statusCode == HttpStatusCode.PreconditionFailed)
             }
             // Check balance didn't change for both parties.
-            bankBalance = getBalance("admin", true)
+            bankBalance = getBalance("admin")
             assert(bankBalance == parseDecimal("4"))
-            val fooBalance = getBalance("foo", true)
+            val fooBalance = getBalance("foo")
             assert(fooBalance == parseDecimal("-4"))
         }
     }
diff --git a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt 
b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
index 24070081..8979fef9 100644
--- a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
+++ b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt
@@ -44,7 +44,6 @@ class SandboxCircuitApiTest {
                     expectSuccess = true
                     basicAuth("foo", "foo")
                 }
-                println(R.bodyAsText())
                 val mapper = ObjectMapper()
                 val respJson = mapper.readTree(R.bodyAsText())
                 val creditAmount = respJson.get("amount_credit").asText()
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
index e9a87cad..8cd2750e 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
@@ -217,7 +217,7 @@ fun circuitApi(circuitRoute: Route) {
     // Abort a cash-out operation.
     circuitRoute.post("/cashouts/{uuid}/abort") {
         call.request.basicAuth() // both admin and author allowed
-        val arg = call.getUriComponent("uuid")
+        val arg = call.expectUriComponent("uuid")
         // Parse and check the UUID.
         val maybeUuid = parseUuid(arg)
         val maybeOperation = transaction {
@@ -244,7 +244,7 @@ fun circuitApi(circuitRoute: Route) {
         if (user == "admin" || user == "bank")
             throw conflict("Institutional user '$user' shouldn't confirm any 
cash-out.")
         // Get the operation identifier.
-        val operationUuid = parseUuid(call.getUriComponent("uuid"))
+        val operationUuid = parseUuid(call.expectUriComponent("uuid"))
         val op = transaction {
             CashoutOperationEntity.find {
                 uuid eq operationUuid
@@ -302,7 +302,7 @@ fun circuitApi(circuitRoute: Route) {
     // Retrieve the status of a cash-out operation.
     circuitRoute.get("/cashouts/{uuid}") {
         call.request.basicAuth() // both admin and author
-        val operationUuid = call.getUriComponent("uuid")
+        val operationUuid = call.expectUriComponent("uuid")
         // Parse and check the UUID.
         val maybeUuid = parseUuid(operationUuid)
         // Get the operation from the database.
@@ -449,7 +449,11 @@ fun circuitApi(circuitRoute: Route) {
                     " but ${amountCredit.amount} was specified.")
         }
         // check that the balance is sufficient
-        val balance = getBalance(user, withPending = true)
+        val balance = getBalance(
+            user,
+            demobank.name,
+            withPending = true
+        )
         val balanceCheck = balance - amountDebitAsNumber
         if (balanceCheck < BigDecimal.ZERO && balanceCheck.abs() > 
BigDecimal(demobank.config.usersDebtLimit))
             throw SandboxError(
@@ -547,7 +551,7 @@ fun circuitApi(circuitRoute: Route) {
     // Get Circuit-relevant account data.
     circuitRoute.get("/accounts/{resourceName}") {
         val username = call.request.basicAuth()
-        val resourceName = call.getUriComponent("resourceName")
+        val resourceName = call.expectUriComponent("resourceName")
         throwIfInstitutionalName(resourceName)
         if (!allowOwnerOrAdmin(username, resourceName)) throw forbidden(
             "User $username has no rights over $resourceName"
@@ -593,6 +597,7 @@ fun circuitApi(circuitRoute: Route) {
             "%${maybeFilter}%"
         } else "%"
         val customers = mutableListOf<Any>()
+        val demobank = ensureDemobank(call)
         transaction {
             DemobankCustomerEntity.find{
                 // like() is case insensitive.
@@ -602,10 +607,13 @@ fun circuitApi(circuitRoute: Route) {
                     val username = it.username
                     val name = it.name
                     val balance = getBalanceForJson(
-                        getBalance(it.username),
-                        getDefaultDemobank().config.currency
+                        getBalance(it.username, demobank.name),
+                        demobank.config.currency
+                    )
+                    val debitThreshold = getMaxDebitForUser(
+                        it.username,
+                        demobank.name
                     )
-                    val debitThreshold = getMaxDebitForUser(it.username)
                 })
             }
         }
@@ -620,7 +628,7 @@ fun circuitApi(circuitRoute: Route) {
     // Change password.
     circuitRoute.patch("/accounts/{customerUsername}/auth") {
         val username = call.request.basicAuth()
-        val customerUsername = call.getUriComponent("customerUsername")
+        val customerUsername = call.expectUriComponent("customerUsername")
         throwIfInstitutionalName(customerUsername)
         if (!allowOwnerOrAdmin(username, customerUsername)) throw forbidden(
             "User $username has no rights over $customerUsername"
@@ -644,7 +652,7 @@ fun circuitApi(circuitRoute: Route) {
         val username = call.request.basicAuth()
         if (username == null)
             throw internalServerError("Authentication disabled, don't have a 
default for this request.")
-        val resourceName = call.getUriComponent("resourceName")
+        val resourceName = call.expectUriComponent("resourceName")
         throwIfInstitutionalName(resourceName)
         if(!allowOwnerOrAdmin(username, resourceName)) throw forbidden(
             "User $username has no rights over $resourceName"
@@ -719,7 +727,8 @@ fun circuitApi(circuitRoute: Route) {
                 username = req.username,
                 password = req.password,
                 name = req.name,
-                iban = req.internal_iban
+                iban = req.internal_iban,
+                demobank = ensureDemobank(call).name
             )
             newAccount.customer.phone = req.contact_data.phone
             newAccount.customer.email = req.contact_data.email
@@ -736,7 +745,7 @@ fun circuitApi(circuitRoute: Route) {
     // Only Admin and only when balance is zero.
     circuitRoute.delete("/accounts/{resourceName}") {
         call.request.basicAuth(onlyAdmin = true)
-        val resourceName = call.getUriComponent("resourceName")
+        val resourceName = call.expectUriComponent("resourceName")
         throwIfInstitutionalName(resourceName)
         val customer = getCustomer(resourceName)
         val bankAccount = getBankAccountFromLabel(
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 798a7761..051d1a09 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -281,7 +281,8 @@ fun buildCamtString(
     subscriberIban: String,
     history: MutableList<RawPayment>,
     balancePrcd: BigDecimal, // Balance up to freshHistory (excluded).
-    balanceClbd: BigDecimal
+    balanceClbd: BigDecimal,
+    currency: String
 ): SandboxCamt {
     /**
      * ID types required:
@@ -300,7 +301,6 @@ fun buildCamtString(
     val zonedDateTime = camtCreationTime.toZonedString()
     val creationTimeMillis = camtCreationTime.toInstant().toEpochMilli()
     val messageId = "sandbox-${creationTimeMillis / 
1000}-${getRandomString(10)}"
-    val currency = getDefaultDemobank().config.currency
 
     val camtMessage = constructXml(indent = true) {
         root("Document") {
@@ -561,7 +561,8 @@ private fun constructCamtResponse(
             bankAccount.iban,
             history,
             balancePrcd = prcdBalance,
-            balanceClbd = clbdBalance
+            balanceClbd = clbdBalance,
+            bankAccount.demoBank.config.currency
         )
         val paymentsList: String = if (logger.isDebugEnabled) {
             var ret = " It includes the payments:"
@@ -713,8 +714,9 @@ private fun parsePain001(paymentRequest: String): 
PainParseResult {
  * payments outside of the running Sandbox and (2) may ease
  * tests where the preparation logic can skip creating also
  * the receiver account.  */
-private fun handleCct(paymentRequest: String,
-                      requestingSubscriber: EbicsSubscriberEntity
+private fun handleCct(
+    paymentRequest: String,
+    requestingSubscriber: EbicsSubscriberEntity
 ) {
     val parseResult = parsePain001(paymentRequest)
     logger.debug("Handling Pain.001: ${parseResult.pmtInfId}, " +
@@ -752,7 +754,7 @@ private fun handleCct(paymentRequest: String,
             logger.warn("Although PAIN validated, BigDecimal didn't parse its 
amount (${parseResult.amount})!")
             throw EbicsProcessingError("The CCT request contains an invalid 
amount: ${parseResult.amount}")
         }
-        if (maybeDebit(bankAccount.label, maybeAmount))
+        if (maybeDebit(bankAccount.label, maybeAmount, 
bankAccount.demoBank.name))
             throw EbicsAmountCheckError("The requested amount 
(${parseResult.amount}) would exceed the debit threshold")
 
         // Get the two parties.
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index 912c2ee6..8fe70541 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -82,8 +82,8 @@ fun insertNewAccount(username: String,
                      password: String,
                      name: String? = null, // tests do not usually give one.
                      iban: String? = null,
-                     isPublic: Boolean = false,
-                     demobank: String = "default"): AccountPair {
+                     demobank: String = "default",
+                     isPublic: Boolean = false): AccountPair {
     requireValidResourceName(username)
     // Forbid institutional usernames.
     if (username == "bank" || username == "admin") {
@@ -163,7 +163,7 @@ fun allowOwnerOrAdmin(username: String?, bankAccountLabel: 
String): Boolean {
  *
  * Return:
  * - null if the authentication is disabled (during tests, for example).
- *   This facilitates tests because allows requests to lack entirely a
+ *   This facilitates tests because allows requests to lack entirely an
  *   Authorization header.
  * - the username of the authenticated user
  * - throw exception when the authentication fails
@@ -365,10 +365,12 @@ fun getBankAccountFromLabel(
         withBankFault
     )
 }
+
+// Get bank account DAO, given its name and demobank.
 fun getBankAccountFromLabel(
     label: String,
     demobank: DemobankConfigEntity,
-    withBankFault: Boolean = false
+    withBankFault: Boolean = false // documented along the other same-named 
function.
 ): BankAccountEntity {
     val maybeBankAccount = transaction {
         BankAccountEntity.find(
@@ -408,7 +410,7 @@ fun BankAccountEntity.bonus(amount: String) {
 }
 
 fun ensureDemobank(call: ApplicationCall): DemobankConfigEntity {
-    return ensureDemobank(call.getUriComponent("demobankid"))
+    return ensureDemobank(call.expectUriComponent("demobankid"))
 }
 
 fun ensureDemobank(name: String): DemobankConfigEntity {
@@ -443,22 +445,6 @@ fun getEbicsSubscriberFromDetails(userID: String, 
partnerID: String, hostID: Str
     }
 }
 
-/**
- * This helper tries to:
- * 1.  Authenticate the client.
- * 2.  Extract the bank account's label from the request's path
- * 3.  Return the bank account DB object if the client has access to it.
- */
-fun getBankAccountWithAuth(call: ApplicationCall): BankAccountEntity {
-    val username = call.request.basicAuth()
-    val accountAccessed = call.getUriComponent("account_name")
-    val demobank = ensureDemobank(call)
-    val bankAccount = getBankAccountFromLabel(accountAccessed, demobank)
-    if (WITH_AUTH && (bankAccount.owner != username && username != "admin"))
-        throw forbidden("Customer '$username' cannot access bank account 
'$accountAccessed'")
-    return bankAccount
-}
-
 /**
  * Compress, encrypt, encode a EBICS payload.  The payload
  * is assumed to be a Zip archive with only one entry.
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 86b7e3a7..5bceaf6b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -48,7 +48,6 @@ import io.ktor.server.plugins.callloging.*
 import io.ktor.server.plugins.cors.routing.*
 import io.ktor.util.date.*
 import org.jetbrains.exposed.sql.*
-import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
 import org.jetbrains.exposed.sql.statements.api.ExposedBlob
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.slf4j.Logger
@@ -61,10 +60,6 @@ import java.math.BigDecimal
 import java.net.URL
 import java.security.interfaces.RSAPublicKey
 import javax.xml.bind.JAXBContext
-import kotlin.reflect.KProperty0
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.reflect.full.declaredMembers
 import kotlin.system.exitProcess
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
@@ -269,7 +264,8 @@ class Camt053Tick : CliktCommand(
                     accountIter.iban,
                     newStatements[accountIter.label]!!,
                     balanceClbd = balanceClbd,
-                    balancePrcd = lastBalance
+                    balancePrcd = lastBalance,
+                    currency = accountIter.demoBank.config.currency
                 )
                 BankAccountStatementEntity.new {
                     statementId = camtData.messageId
@@ -717,7 +713,7 @@ val sandboxApp: Application.() -> Unit = {
         // Information about one bank account.
         get("/admin/bank-accounts/{label}") {
             val username = call.request.basicAuth()
-            val label = call.getUriComponent("label")
+            val label = call.expectUriComponent("label")
             val ret = transaction {
                 val demobank = getDefaultDemobank()
                 val bankAccount = getBankAccountFromLabel(label, demobank)
@@ -1069,9 +1065,7 @@ val sandboxApp: Application.() -> Unit = {
         }
         // Process one EBICS request
         post("/ebicsweb") {
-            try {
-                call.ebicsweb()
-            }
+            try { call.ebicsweb() }
             /**
              * The catch blocks try to extract a EBICS error message from the
              * exception type being handled.  NOT logging under each catch 
block
@@ -1139,17 +1133,13 @@ val sandboxApp: Application.() -> Unit = {
             // NOTE: TWG assumes that username == bank account label.
             route("/taler-wire-gateway") {
                 post("/{exchangeUsername}/admin/add-incoming") {
-                    val username = call.getUriComponent("exchangeUsername")
+                    val username = call.expectUriComponent("exchangeUsername")
                     val usernameAuth = call.request.basicAuth()
-                    if (username != usernameAuth) {
-                        throw forbidden(
-                            "Bank account name and username differ: $username 
vs $usernameAuth"
-                        )
-                    }
+                    if (username != usernameAuth)
+                        throw forbidden("Bank account name and username 
differ: $username vs $usernameAuth")
                     logger.debug("TWG add-incoming passed authentication")
-                    val body = try {
-                        call.receive<TWGAdminAddIncoming>()
-                    } catch (e: Exception) {
+                    val body = try { call.receive<TWGAdminAddIncoming>() }
+                    catch (e: Exception) {
                         logger.error("/admin/add-incoming failed at parsing 
the request body")
                         throw SandboxError(
                             HttpStatusCode.BadRequest,
@@ -1239,7 +1229,7 @@ val sandboxApp: Application.() -> Unit = {
                         )
                     }
                     val demobank = ensureDemobank(call)
-                    var captcha_page = demobank.config.captchaUrl
+                    val captcha_page = demobank.config.captchaUrl
                     if (captcha_page == null) logger.warn("CAPTCHA URL not 
found")
                     val ret = TalerWithdrawalStatus(
                         selection_done = maybeWithdrawalOp.selectionDone,
@@ -1259,7 +1249,16 @@ val sandboxApp: Application.() -> Unit = {
             // Talk to Web UI.
             route("/access-api") {
                 post("/accounts/{account_name}/transactions") {
-                    val bankAccount = getBankAccountWithAuth(call)
+                    val username = call.request.basicAuth()
+                    val demobank = ensureDemobank(call)
+                    val bankAccount = getBankAccountFromLabel(
+                        call.expectUriComponent("account_name"),
+                        demobank
+                    )
+                    // note: admin has no rights to create transactions on 
non-admin accounts.
+                    val authGranted: Boolean = !WITH_AUTH
+                    if (!authGranted && username != bankAccount.label)
+                        throw unauthorized("Username '$username' has no rights 
over bank account ${bankAccount.label}")
                     val req = call.receive<NewTransactionReq>()
                     val payto = parsePayto(req.paytoUri)
                     val amount: String? = payto.amount ?: req.amount
@@ -1283,8 +1282,7 @@ val sandboxApp: Application.() -> Unit = {
                 }
                 // Information about one withdrawal.
                 get("/accounts/{account_name}/withdrawals/{withdrawal_id}") {
-                    val op = 
getWithdrawalOperation(call.getUriComponent("withdrawal_id"))
-                    ensureDemobank(call)
+                    val op = 
getWithdrawalOperation(call.expectUriComponent("withdrawal_id"))
                     if (!op.selectionDone && op.reservePub != null) throw 
internalServerError(
                         "Unselected withdrawal has a reserve public key",
                         LibeufinErrorCode.LIBEUFIN_EC_INCONSISTENT_STATE
@@ -1302,29 +1300,30 @@ val sandboxApp: Application.() -> Unit = {
                 // Create a new withdrawal operation.
                 post("/accounts/{account_name}/withdrawals") {
                     var username = call.request.basicAuth()
-                    if (username == null && (!WITH_AUTH)) {
-                        logger.info("Authentication is disabled to facilitate 
tests, defaulting to 'admin' username")
-                        username = "admin"
-                    }
                     val demobank = ensureDemobank(call)
                     /**
                      * Check here if the user has the right over the claimed 
bank account.  After
                      * this check, the withdrawal operation will be allowed 
only by providing its
                      * UID. */
                     val maybeOwnedAccount = getBankAccountFromLabel(
-                        call.getUriComponent("account_name"),
+                        call.expectUriComponent("account_name"),
                         demobank
                     )
-                    if (maybeOwnedAccount.owner != username && WITH_AUTH) 
throw unauthorized(
-                        "Customer '$username' has no rights over bank account 
'${maybeOwnedAccount.label}'"
-                    )
+                    val authGranted = !WITH_AUTH // note: admin not allowed on 
non-admin accounts
+                    if (!authGranted && maybeOwnedAccount.owner != username)
+                        throw unauthorized("Customer '$username' has no rights 
over bank account '${maybeOwnedAccount.label}'")
                     val req = call.receive<WithdrawalRequest>()
                     // Check for currency consistency
                     val amount = parseAmount(req.amount)
                     if (amount.currency != demobank.config.currency)
                         throw badRequest("Currency ${amount.currency} differs 
from Demobank's: ${demobank.config.currency}")
                     // Check funds are sufficient.
-                    if (maybeDebit(maybeOwnedAccount.label, 
BigDecimal(amount.amount))) {
+                    if (
+                        maybeDebit(
+                            maybeOwnedAccount.label,
+                            BigDecimal(amount.amount),
+                            transaction { maybeOwnedAccount.demoBank.name }
+                        )) {
                         logger.error("Account ${maybeOwnedAccount.label} would 
surpass debit threshold.  Not withdrawing")
                         throw SandboxError(HttpStatusCode.Forbidden, 
"Insufficient funds")
                     }
@@ -1372,7 +1371,7 @@ val sandboxApp: Application.() -> Unit = {
                 }
                 // Confirm a withdrawal: no basic auth, because the ID should 
be unguessable.
                 
post("/accounts/{account_name}/withdrawals/{withdrawal_id}/confirm") {
-                    val withdrawalId = call.getUriComponent("withdrawal_id")
+                    val withdrawalId = call.expectUriComponent("withdrawal_id")
                     transaction {
                         val wo = getWithdrawalOperation(withdrawalId)
                         if (wo.aborted) throw SandboxError(
@@ -1415,7 +1414,7 @@ val sandboxApp: Application.() -> Unit = {
                     return@post
                 }
                 
post("/accounts/{account_name}/withdrawals/{withdrawal_id}/abort") {
-                    val withdrawalId = call.getUriComponent("withdrawal_id")
+                    val withdrawalId = call.expectUriComponent("withdrawal_id")
                     val operation = getWithdrawalOperation(withdrawalId)
                     if (operation.confirmationDone) throw conflict("Cannot 
abort paid withdrawal.")
                     transaction { operation.aborted = true }
@@ -1425,16 +1424,12 @@ val sandboxApp: Application.() -> Unit = {
                 // Bank account basic information.
                 get("/accounts/{account_name}") {
                     val username = call.request.basicAuth()
-                    val accountAccessed = call.getUriComponent("account_name")
+                    val accountAccessed = 
call.expectUriComponent("account_name")
                     val demobank = ensureDemobank(call)
                     val bankAccount = getBankAccountFromLabel(accountAccessed, 
demobank)
-                    // Check rights.
-                    if (
-                        WITH_AUTH
-                        && (bankAccount.owner != username && username != 
"admin")
-                    ) throw forbidden(
-                            "Customer '$username' cannot access bank account 
'$accountAccessed'"
-                        )
+                    val authGranted = !WITH_AUTH || bankAccount.isPublic || 
username == "admin"
+                    if (!authGranted && bankAccount.owner != username)
+                        throw forbidden("Customer '$username' cannot access 
bank account '$accountAccessed'")
                     val balance = getBalance(bankAccount, withPending = true)
                     call.respond(object {
                         val balance = object {
@@ -1450,21 +1445,23 @@ val sandboxApp: Application.() -> Unit = {
                         val iban = bankAccount.iban
                         // The Elvis operator helps the --no-auth case,
                         // where username would be empty
-                        val debitThreshold = getMaxDebitForUser(username ?: 
"admin").toString()
+                        val debitThreshold = getMaxDebitForUser(
+                            username = username ?: "admin",
+                            demobankName = demobank.name
+                        ).toString()
                     })
                     return@get
                 }
                 get("/accounts/{account_name}/transactions/{tId}") {
+                    val username = call.request.basicAuth()
                     val demobank = ensureDemobank(call)
                     val bankAccount = getBankAccountFromLabel(
-                        call.getUriComponent("account_name"),
+                        call.expectUriComponent("account_name"),
                         demobank
                     )
-                    val authOk: Boolean = bankAccount.isPublic || (!WITH_AUTH)
-                    if (!authOk && (call.request.basicAuth() != 
bankAccount.owner)) throw forbidden(
-                        "Cannot access bank account ${bankAccount.label}"
-                    )
-                    // Flow here == Right on the bank account.
+                    val authGranted: Boolean = bankAccount.isPublic || 
!WITH_AUTH || username == "admin"
+                    if (!authGranted && username != bankAccount.owner)
+                        throw forbidden("Cannot access bank account 
${bankAccount.label}")
                     val tId = call.parameters["tId"] ?: throw badRequest("URI 
didn't contain the transaction ID")
                     val tx: BankAccountTransactionEntity? = transaction {
                         BankAccountTransactionEntity.find {
@@ -1476,16 +1473,15 @@ val sandboxApp: Application.() -> Unit = {
                     return@get
                 }
                 get("/accounts/{account_name}/transactions") {
+                    val username = call.request.basicAuth()
                     val demobank = ensureDemobank(call)
                     val bankAccount = getBankAccountFromLabel(
-                        call.getUriComponent("account_name"),
+                        call.expectUriComponent("account_name"),
                         demobank
                     )
-                    val authOk: Boolean = bankAccount.isPublic || (!WITH_AUTH)
-                    if (!authOk && (call.request.basicAuth() != 
bankAccount.owner)) throw forbidden(
-                        "Cannot access bank account ${bankAccount.label}"
-                    )
-
+                    val authGranted: Boolean = bankAccount.isPublic || 
!WITH_AUTH || username == "admin"
+                    if (!authGranted && bankAccount.owner != username)
+                        throw forbidden("Cannot access bank account 
${bankAccount.label}")
                     val page: Int = 
Integer.decode(call.request.queryParameters["page"] ?: "0")
                     val size: Int = 
Integer.decode(call.request.queryParameters["size"] ?: "5")
 
@@ -1535,7 +1531,10 @@ val sandboxApp: Application.() -> Unit = {
                                     BankAccountsTable.demoBank eq demobank.id
                             )
                         }.forEach {
-                            val balanceIter = getBalance(it, withPending = 
true)
+                            val balanceIter = getBalance(
+                                it,
+                                withPending = true,
+                            )
                             ret.publicAccounts.add(
                                 PublicAccountInfo(
                                     balance = 
"${demobank.config.currency}:$balanceIter",
@@ -1549,10 +1548,21 @@ val sandboxApp: Application.() -> Unit = {
                     return@get
                 }
                 delete("accounts/{account_name}") {
-                    // Check demobank was created.
-                    ensureDemobank(call)
+                    val username = call.request.basicAuth()
+                    val demobank = ensureDemobank(call)
+                    val authGranted = !WITH_AUTH || username == "admin"
+                    val bankAccountLabel = 
call.expectUriComponent("account_name")
+                    /**
+                     * This helper fails if the demobank that is mentioned in 
the URI
+                     * is not hosting the account to be deleted.
+                     */
+                    val bankAccount = getBankAccountFromLabel(
+                        bankAccountLabel,
+                        demobank
+                    )
+                    if (!authGranted && username != bankAccount.owner)
+                        throw unauthorized("User '$username' has no rights to 
delete bank account '$bankAccountLabel'")
                     transaction {
-                        val bankAccount = getBankAccountWithAuth(call)
                         val customerAccount = getCustomer(bankAccount.owner)
                         bankAccount.delete()
                         customerAccount.delete()
@@ -1572,11 +1582,12 @@ val sandboxApp: Application.() -> Unit = {
                     }
                     val req = call.receive<CustomerRegistration>()
                     val newAccount = insertNewAccount(
-                            req.username,
-                            req.password,
-                            name = req.name,
-                            iban = req.iban,
-                            isPublic = req.isPublic
+                        req.username,
+                        req.password,
+                        name = req.name,
+                        iban = req.iban,
+                        demobank = demobank.name,
+                        isPublic = req.isPublic
                     )
                     val balance = getBalance(newAccount.bankAccount, 
withPending = true)
                     call.respond(object {
@@ -1587,7 +1598,10 @@ val sandboxApp: Application.() -> Unit = {
                             receiverName = 
getPersonNameFromCustomer(req.username)
                         )
                         val iban = newAccount.bankAccount.iban
-                        val debitThreshold = 
getMaxDebitForUser(req.username).toString()
+                        val debitThreshold = getMaxDebitForUser(
+                            req.username,
+                            demobank.name
+                        ).toString()
                     })
                     return@post
                 }
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
index d62f0b0f..2361b876 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -19,7 +19,7 @@ fun maybeDebit(
         "Demobank '${demobankName}' not found when trying to check the debit 
threshold" +
                 " for user $accountLabel"
     )
-    val balance = getBalance(accountLabel, withPending = true)
+    val balance = getBalance(accountLabel, demobankName, withPending = true)
     val maxDebt = if (accountLabel == "admin") {
         demobank.config.bankDebtLimit
     } else demobank.config.usersDebtLimit
@@ -32,8 +32,13 @@ fun maybeDebit(
     return false
 }
 
-fun getMaxDebitForUser(username: String): Int {
-    val bank = getDefaultDemobank()
+fun getMaxDebitForUser(
+    username: String,
+    demobankName: String = "default"
+): Int {
+    val bank = getDemobank(demobankName) ?: throw internalServerError(
+        "demobank $demobankName not found"
+    )
     if (username == "admin") return bank.config.bankDebtLimit
     return bank.config.usersDebtLimit
 }
@@ -50,6 +55,9 @@ fun getBalanceForJson(value: BigDecimal, currency: String): 
BalanceJson {
  * last statement.  If the bank account does not have any statement
  * yet, then zero is returned.  When 'withPending' is true, it adds
  * the pending transactions to it.
+ *
+ * Note: because transactions are searched after the bank accounts
+ * (numeric) id, the research in the database is not ambiguous.
  */
 fun getBalance(
     bankAccount: BankAccountEntity,
@@ -92,10 +100,16 @@ fun getBalance(
     return lastBalance
 }
 
-// Wrapper offering to get bank accounts from a string.
-fun getBalance(accountLabel: String, withPending: Boolean = true): BigDecimal {
-    val defaultDemobank = getDefaultDemobank()
-    val account = getBankAccountFromLabel(accountLabel, defaultDemobank)
+// Gets the balance of 'accountLabel', which is hosted at 'demobankName'.
+fun getBalance(accountLabel: String,
+               demobankName: String = "default",
+               withPending: Boolean = true
+): BigDecimal {
+    val demobank = getDemobank(demobankName) ?: throw SandboxError(
+        HttpStatusCode.InternalServerError,
+        "Demobank '$demobankName' not found"
+    )
+    val account = getBankAccountFromLabel(accountLabel, demobank)
     return getBalance(account, withPending)
 }
 
@@ -150,7 +164,12 @@ fun wireTransfer(
                     "  Only ${demobank.config.currency} allowed."
         )
     // Check funds are sufficient.
-    if (maybeDebit(debitAccount.label, amountAsNumber)) {
+    if (
+        maybeDebit(
+            debitAccount.label,
+            amountAsNumber,
+            demobank.name
+    )) {
         logger.error("Account ${debitAccount.label} would surpass debit 
threshold.  Rollback wire transfer")
         throw SandboxError(HttpStatusCode.PreconditionFailed, "Insufficient 
funds")
     }
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index 6763db79..85e592ab 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -118,7 +118,7 @@ fun ApplicationRequest.getBaseUrl(): String {
  * Get the URI (path's) component or throw Internal server error.
  * @param component the name of the URI component to return.
  */
-fun ApplicationCall.getUriComponent(name: String): String {
+fun ApplicationCall.expectUriComponent(name: String): String {
     val ret: String? = this.parameters[name]
     if (ret == null) throw badRequest("Component $name not found in URI")
     return ret

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]