[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: pointing demobanks,
gnunet <=