[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: Group Integration/Access API under Dem
From: |
gnunet |
Subject: |
[libeufin] branch master updated: Group Integration/Access API under Demobank route. |
Date: |
Wed, 20 Oct 2021 13:15:34 +0200 |
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 7a6f266 Group Integration/Access API under Demobank route.
7a6f266 is described below
commit 7a6f2665600cf3c33e0f51507fb28a53c08a39d8
Author: ms <ms@taler.net>
AuthorDate: Wed Oct 20 13:15:00 2021 +0200
Group Integration/Access API under Demobank route.
---
.../src/main/kotlin/tech/libeufin/sandbox/DB.kt | 31 ++-
.../main/kotlin/tech/libeufin/sandbox/Helpers.kt | 139 +++++-----
.../src/main/kotlin/tech/libeufin/sandbox/JSON.kt | 14 +-
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 285 ++++++++++-----------
.../kotlin/tech/libeufin/sandbox/bankAccount.kt | 19 +-
util/src/main/kotlin/HTTP.kt | 2 +-
util/src/main/kotlin/JSON.kt | 2 +
util/src/main/kotlin/Payto.kt | 6 +-
8 files changed, 256 insertions(+), 242 deletions(-)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 2433d48..63cf772 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -94,6 +94,7 @@ object DemobankConfigsTable : LongIdTable() {
val bankDebtLimit = integer("bankDebtLimit")
val usersDebtLimit = integer("usersDebtLimit")
val name = text("hostname")
+ val suggestedExchange = text("suggestedExchange").nullable()
}
class DemobankConfigEntity(id: EntityID<Long>) : LongEntity(id) {
@@ -103,6 +104,7 @@ class DemobankConfigEntity(id: EntityID<Long>) :
LongEntity(id) {
var bankDebtLimit by DemobankConfigsTable.bankDebtLimit
var usersDebtLimit by DemobankConfigsTable.usersDebtLimit
var name by DemobankConfigsTable.name
+ var suggestedExchange by DemobankConfigsTable.suggestedExchange
}
/**
@@ -110,9 +112,7 @@ class DemobankConfigEntity(id: EntityID<Long>) :
LongEntity(id) {
* Created via the /demobanks/{demobankname}/register endpoint.
*/
object DemobankCustomersTable : LongIdTable() {
- val isPublic = bool("isPublic").default(false)
val demobankConfig = reference("demobankConfig", DemobankConfigsTable)
- val bankAccount = reference("bankAccount", BankAccountsTable)
val username = text("username")
val passwordHash = text("passwordHash")
val name = text("name").nullable()
@@ -120,9 +120,7 @@ object DemobankCustomersTable : LongIdTable() {
class DemobankCustomerEntity(id: EntityID<Long>) : LongEntity(id) {
companion object :
LongEntityClass<DemobankCustomerEntity>(DemobankCustomersTable)
- var isPublic by DemobankCustomersTable.isPublic
var demobankConfig by DemobankConfigEntity referencedOn
DemobankCustomersTable.demobankConfig
- var bankAccount by BankAccountEntity referencedOn
DemobankCustomersTable.bankAccount
var username by DemobankCustomersTable.username
var passwordHash by DemobankCustomersTable.passwordHash
var name by DemobankCustomersTable.name
@@ -316,7 +314,10 @@ object BankAccountTransactionsTable : LongIdTable() {
val debtorBic = text("debtorBic").nullable()
val debtorName = text("debtorName")
val subject = text("subject")
- val amount = text("amount") // NOT the usual $currency:x.y, but a BigInt
as string
+ /**
+ * Amount is a stringified BigInt
+ */
+ val amount = text("amount")
val currency = text("currency")
val date = long("date")
@@ -344,7 +345,6 @@ class BankAccountTransactionEntity(id: EntityID<Long>) :
LongEntity(id) {
return freshTx
}
}
-
var creditorIban by BankAccountTransactionsTable.creditorIban
var creditorBic by BankAccountTransactionsTable.creditorBic
var creditorName by BankAccountTransactionsTable.creditorName
@@ -372,6 +372,13 @@ object BankAccountsTable : IntIdTable() {
val currency = text("currency")
val isDebit = bool("isDebit").default(false)
val balance = text("balance")
+ /**
+ * Allow to assign "admin" - who doesn't have a customer DB entry -
+ * as the owner. That allows tests using the --no-auth option to go on.
+ */
+ val owner = text("owner")
+ val isPublic = bool("isPublic")
+ val demoBank = reference("demoBank", DemobankConfigsTable)
}
class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) {
@@ -383,6 +390,9 @@ class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) {
var currency by BankAccountsTable.currency
var isDebit by BankAccountsTable.isDebit
var balance by BankAccountsTable.balance
+ var owner by BankAccountsTable.owner
+ var isPublic by BankAccountsTable.isPublic
+ var demoBank by BankAccountsTable.demoBank
}
object BankAccountStatementsTable : IntIdTable() {
@@ -410,13 +420,13 @@ class BankAccountStatementEntity(id: EntityID<Int>) :
IntEntity(id) {
object TalerWithdrawalsTable : LongIdTable() {
val wopid = uuid("wopid").autoGenerate()
-
+ val amount = text("amount") // $currency:x.y
/**
* Turns to true after the wallet gave the reserve public key
* and the exchange details to the bank.
*/
val selectionDone = bool("selectionDone").default(false)
-
+ val aborted = bool("aborted").default(false)
/**
* Turns to true after the wire transfer to the exchange bank account
* gets completed _on the bank's side_. This does never guarantees that
@@ -425,7 +435,7 @@ object TalerWithdrawalsTable : LongIdTable() {
val transferDone = bool("transferDone").default(false)
val reservePub = text("reservePub").nullable()
val selectedExchangePayto = text("selectedExchangePayto").nullable()
-
+ val walletBankAccount = reference("walletBankAccount", BankAccountsTable)
}
class TalerWithdrawalEntity(id: EntityID<Long>) : LongEntity(id) {
companion object :
LongEntityClass<TalerWithdrawalEntity>(TalerWithdrawalsTable)
@@ -434,6 +444,9 @@ class TalerWithdrawalEntity(id: EntityID<Long>) :
LongEntity(id) {
var transferDone by TalerWithdrawalsTable.transferDone
var reservePub by TalerWithdrawalsTable.reservePub
var selectedExchangePayto by TalerWithdrawalsTable.selectedExchangePayto
+ var amount by TalerWithdrawalsTable.amount
+ var walletBankAccount by BankAccountEntity referencedOn
TalerWithdrawalsTable.walletBankAccount
+ var aborted by TalerWithdrawalsTable.aborted
}
object BankAccountReportsTable : IntIdTable() {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index a63c363..f8c4676 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -23,7 +23,8 @@ import io.ktor.http.HttpStatusCode
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
-import tech.libeufin.util.internalServerError
+import tech.libeufin.util.*
+import javax.security.auth.Subject
/**
* Helps to communicate Camt values without having
@@ -57,11 +58,81 @@ fun getOrderTypeFromTransactionId(transactionID: String):
String {
return uploadTransaction.orderType
}
+/**
+ * Book a CRDT and a DBIT transaction and return the unique reference thereof.
+ *
+ * At the moment there is redundancy because all the creditor / debtor details
+ * are contained (directly or indirectly) already in the BankAccount
parameters.
+ *
+ * This is kept both not to break the existing tests and to allow future
versions
+ * where one party of the transaction is not a customer of the running Sandbox.
+ */
+
+fun wireTransfer(
+ debitAccount: BankAccountEntity,
+ creditAccount: BankAccountEntity,
+ demoBank: DemobankConfigEntity,
+ subject: String,
+ amount: String,
+): String {
+
+ fun getOwnerName(ownerUsername: String): String {
+ return if (creditAccount.owner == "admin") "admin" else {
+ val creditorCustomer = DemobankCustomerEntity.find(
+ DemobankCustomersTable.username eq creditAccount.owner
+ ).firstOrNull() ?: throw internalServerError(
+ "Owner of bank account '${creditAccount.label}' not found"
+ )
+ creditorCustomer.name ?: "Name not given"
+ }
+ }
+ val timeStamp = getUTCnow().toInstant().toEpochMilli()
+ val transactionRef = getRandomString(8)
+ transaction {
+ BankAccountTransactionEntity.new {
+ creditorIban = creditAccount.iban
+ creditorBic = creditAccount.bic
+ this.creditorName = getOwnerName(creditAccount.owner)
+ debtorIban = debitAccount.iban
+ debtorBic = debitAccount.bic
+ debtorName = getOwnerName(debitAccount.owner)
+ this.subject = subject
+ this.amount = amount
+ this.currency = demoBank.currency
+ date = timeStamp
+ accountServicerReference = transactionRef
+ account = creditAccount
+ direction = "CRDT"
+ }
+ BankAccountTransactionEntity.new {
+ creditorIban = creditAccount.iban
+ creditorBic = creditAccount.bic
+ this.creditorName = getOwnerName(creditAccount.owner)
+ debtorIban = debitAccount.iban
+ debtorBic = debitAccount.bic
+ debtorName = getOwnerName(debitAccount.owner)
+ this.subject = subject
+ this.amount = amount
+ this.currency = demoBank.currency
+ date = timeStamp
+ accountServicerReference = transactionRef
+ account = debitAccount
+ direction = "DBIT"
+ }
+ }
+ return transactionRef
+}
+
+
+fun getBankAccountFromPayto(paytoUri: String): BankAccountEntity {
+ val paytoParse = parsePayto(paytoUri)
+ return getBankAccountFromIban(paytoParse.iban)
+
+}
+
fun getBankAccountFromIban(iban: String): BankAccountEntity {
return transaction {
- BankAccountEntity.find(
- BankAccountsTable.iban eq iban
- )
+ BankAccountEntity.find(BankAccountsTable.iban eq iban)
}.firstOrNull() ?: throw SandboxError(
HttpStatusCode.NotFound,
"Did not find a bank account for ${iban}"
@@ -120,62 +191,4 @@ fun getEbicsSubscriberFromDetails(userID: String,
partnerID: String, hostID: Str
"Ebics subscriber not found"
)
}
-}
-
-/**
- * FIXME: commenting out until a solution for i18n is found.
- *
-private fun initJinjava(): Jinjava {
- class JinjaFunctions {
- // Used by templates to retrieve configuration values.
- fun settings_value(name: String): String {
- return "foo"
- }
- fun gettext(translatable: String): String {
- // temporary, just to make the compiler happy.
- return translatable
- }
- fun url(name: String): String {
- val map = mapOf<String, String>(
- "login" to "todo",
- "profile" to "todo",
- "register" to "todo",
- "public-accounts" to "todo"
- )
- return map[name] ?: throw
SandboxError(HttpStatusCode.InternalServerError, "URL name unknown")
- }
- }
- val jinjava = Jinjava()
- val settingsValueFunc = ELFunctionDefinition(
- "tech.libeufin.sandbox", "settings_value",
- JinjaFunctions::class.java, "settings_value", String::class.java
- )
- val gettextFuncAlias = ELFunctionDefinition(
- "tech.libeufin.sandbox", "_",
- JinjaFunctions::class.java, "gettext", String::class.java
- )
- val gettextFunc = ELFunctionDefinition(
- "", "gettext",
- JinjaFunctions::class.java, "gettext", String::class.java
- )
- val urlFunc = ELFunctionDefinition(
- "tech.libeufin.sandbox", "url",
- JinjaFunctions::class.java, "url", String::class.java
- )
-
- jinjava.globalContext.registerFunction(settingsValueFunc)
- jinjava.globalContext.registerFunction(gettextFunc)
- jinjava.globalContext.registerFunction(gettextFuncAlias)
- jinjava.globalContext.registerFunction(urlFunc)
-
- return jinjava
-}
-
-val jinjava = initJinjava()
-
-fun renderTemplate(templateName: String, context: Map<String, String>): String
{
- val template = Resources.toString(Resources.getResource(
- "templates/$templateName"), Charsets.UTF_8
- )
- return jinjava.render(template, context)
-} **/
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index 7756d98..3447c4b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -82,16 +82,10 @@ data class CustomerRegistration(
val password: String
)
-/**
- * More detailed information about one customer. This type
- * is mainly required along public histories and/or customer
- * data unrelated to the Access API.
- */
-data class CustomerInfo(
- val username: String,
- val name: String,
+// Could be used as a general bank account info container.
+data class PublicAccountInfo(
val balance: String,
- val iban: String,
+ val iban: String
// more ..?
)
@@ -112,7 +106,7 @@ data class TalerWithdrawalStatus(
val aborted: Boolean = false,
)
-data class TalerWithdrawalConfirmation(
+data class TalerWithdrawalSelection(
val reserve_pub: String,
val selected_exchange: String?
)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index fe6fcac..d158580 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -63,6 +63,7 @@ import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.util.*
import io.ktor.util.date.*
+import io.ktor.util.pipeline.*
import kotlinx.coroutines.newSingleThreadContext
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
@@ -519,7 +520,7 @@ val sandboxApp: Application.() -> Unit = {
post("/admin/payments/camt") {
call.request.basicAuth()
val body = call.receiveJson<CamtParams>()
- val bankaccount = getAccountFromLabel(body.bankaccount)
+ val bankaccount = getBankAccountFromLabel(body.bankaccount)
if (body.type != 53) throw SandboxError(
HttpStatusCode.NotFound,
"Only Camt.053 documents can be generated."
@@ -540,7 +541,7 @@ val sandboxApp: Application.() -> Unit = {
// create a new bank account, no EBICS relation.
post("/admin/bank-accounts/{label}") {
- call.request.basicAuth()
+ val username = call.request.basicAuth()
val body = call.receiveJson<BankAccountInfo>()
transaction {
BankAccountEntity.new {
@@ -548,6 +549,7 @@ val sandboxApp: Application.() -> Unit = {
bic = body.bic
label = body.label
currency = body.currency ?: "EUR"
+ owner = username ?: "admin" // allows
}
}
call.respond(object {})
@@ -622,7 +624,7 @@ val sandboxApp: Application.() -> Unit = {
// Associates a new bank account with an existing Ebics subscriber.
post("/admin/ebics/bank-accounts") {
- call.request.basicAuth()
+ val username = call.request.basicAuth()
val body = call.receiveJson<BankAccountRequest>()
if (!validateBic(body.bic)) {
throw SandboxError(io.ktor.http.HttpStatusCode.BadRequest,
"invalid BIC (${body.bic})")
@@ -633,18 +635,19 @@ val sandboxApp: Application.() -> Unit = {
body.subscriber.partnerID,
body.subscriber.hostID
)
- val check = tech.libeufin.sandbox.BankAccountEntity.find {
- tech.libeufin.sandbox.BankAccountsTable.iban eq body.iban
or (tech.libeufin.sandbox.BankAccountsTable.label eq body.label)
+ val check = BankAccountEntity.find {
+ BankAccountsTable.iban eq body.iban or
(BankAccountsTable.label eq body.label)
}.count()
if (check > 0) throw SandboxError(
- io.ktor.http.HttpStatusCode.BadRequest,
+ HttpStatusCode.BadRequest,
"Either IBAN or account label were already taken; please
choose fresh ones"
)
- subscriber.bankAccount =
tech.libeufin.sandbox.BankAccountEntity.new {
+ subscriber.bankAccount = BankAccountEntity.new {
iban = body.iban
bic = body.bic
label = body.label
currency = body.currency.uppercase(java.util.Locale.ROOT)
+ owner = username ?: "admin"
}
}
call.respondText("Bank account created")
@@ -656,7 +659,7 @@ val sandboxApp: Application.() -> Unit = {
call.request.basicAuth()
val accounts = mutableListOf<BankAccountInfo>()
transaction {
- tech.libeufin.sandbox.BankAccountEntity.all().forEach {
+ BankAccountEntity.all().forEach {
accounts.add(
BankAccountInfo(
label = it.label,
@@ -725,7 +728,7 @@ val sandboxApp: Application.() -> Unit = {
run {
val amount = kotlin.random.Random.nextLong(5, 25)
- tech.libeufin.sandbox.BankAccountTransactionEntity.new {
+ BankAccountTransactionEntity.new {
creditorIban = account.iban
creditorBic = account.bic
creditorName = "Creditor Name"
@@ -911,90 +914,6 @@ val sandboxApp: Application.() -> Unit = {
val currency = currencyEnv
})
}
- /**
- * not regulating the access here, as the wopid was only granted
- * to logged-in users before (at the /taler endpoint) and has enough
- * entropy to prevent guesses.
- */
- get("/api/withdrawal-operation/{wopid}") {
- val wopid: String = ensureNonNull(call.parameters["wopid"])
- val wo = transaction {
-
- tech.libeufin.sandbox.TalerWithdrawalEntity.find {
- tech.libeufin.sandbox.TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
- }.firstOrNull() ?: throw SandboxError(
- io.ktor.http.HttpStatusCode.NotFound,
- "Withdrawal operation: $wopid not found"
- )
- }
- SandboxAssert(
- envName != null,
- "Env name not found, cannot suggest Exchange."
- )
- val ret = TalerWithdrawalStatus(
- selection_done = wo.selectionDone,
- transfer_done = wo.transferDone,
- amount = "${currencyEnv}:5",
- suggested_exchange = "https://exchange.${envName}.taler.net/"
- )
- call.respond(ret)
- return@get
- }
- /**
- * Here Sandbox collects the reserve public key to be used
- * as the wire transfer subject, and pays the exchange - which
- * is as well collected in this request.
- */
- post("/api/withdrawal-operation/{wopid}") {
-
- val wopid: String = ensureNonNull(call.parameters["wopid"])
- val body = call.receiveJson<TalerWithdrawalConfirmation>()
-
- newSuspendedTransaction(context = singleThreadContext) {
- var wo = tech.libeufin.sandbox.TalerWithdrawalEntity.find {
- tech.libeufin.sandbox.TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
- }.firstOrNull() ?: throw SandboxError(
- io.ktor.http.HttpStatusCode.NotFound, "Withdrawal
operation $wopid not found."
- )
- if (wo.selectionDone) {
- if (wo.transferDone) {
- logger.info("Wallet performs again this operation that
was paid out earlier: idempotent")
- return@newSuspendedTransaction
- }
- // reservePub+exchange selected but not payed: check
consistency
- if (body.reserve_pub != wo.reservePub) throw SandboxError(
- io.ktor.http.HttpStatusCode.Conflict,
- "Selecting a different reserve from the one already
selected"
- )
- if (body.selected_exchange != wo.selectedExchangePayto)
throw SandboxError(
- io.ktor.http.HttpStatusCode.Conflict,
- "Selecting a different exchange from the one already
selected"
- )
- }
- // here only if (1) no selection done or (2) _only_ selection
done:
- // both ways no transfer must have happened.
- SandboxAssert(!wo.transferDone, "Sandbox allowed paid but
unselected reserve")
-
- wireTransfer(
- "sandbox-account-customer",
- "sandbox-account-exchange",
- "$currencyEnv:5",
- body.reserve_pub
- )
- wo.reservePub = body.reserve_pub
- wo.selectedExchangePayto = body.selected_exchange
- wo.selectionDone = true
- wo.transferDone = true
- }
- /**
- * NOTE: is this always guaranteed to run AFTER the suspended
- * transaction block above?
- */
- call.respond(object {
- val transfer_done = true
- })
- return@post
- }
// Create a new demobank instance with a particular currency,
// debt limit and possibly other configuration
@@ -1029,39 +948,93 @@ val sandboxApp: Application.() -> Unit = {
route("/demobanks/{demobankid}") {
+ // Talk to wallets.
+ route("/integration-api") {
+ post("/api/withdrawal-operation/{wopid}") {
+ val wopid: String = ensureNonNull(call.parameters["wopid"])
+ val body = call.receiveJson<TalerWithdrawalSelection>()
+ val transferDone = newSuspendedTransaction(context =
singleThreadContext) {
+ val wo = TalerWithdrawalEntity.find {
+ TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
+ }.firstOrNull() ?: throw SandboxError(
+ HttpStatusCode.NotFound, "Withdrawal operation
$wopid not found."
+ )
+ if (wo.selectionDone) {
+ if (wo.transferDone) {
+ logger.info("Wallet performs again this
operation that was paid out earlier: idempotent")
+ return@newSuspendedTransaction
+ }
+ // Selected already but NOT paid, check
consistency.
+ if (body.reserve_pub != wo.reservePub) throw
SandboxError(
+ HttpStatusCode.Conflict,
+ "Selecting a different reserve from the one
already selected"
+ )
+ if (body.selected_exchange !=
wo.selectedExchangePayto) throw SandboxError(
+ HttpStatusCode.Conflict,
+ "Selecting a different exchange from the one
already selected"
+ )
+ }
+ wo.reservePub = body.reserve_pub
+ wo.selectedExchangePayto = body.selected_exchange
+ wo.selectionDone = true
+ wo.transferDone
+ }
+ call.respond(object {
+ val transfer_done = transferDone
+ })
+ return@post
+ }
+ get("/withdrawal-operation/{wopid}") {
+ val wopid: String = ensureNonNull(call.parameters["wopid"])
+ val wo = transaction {
+ TalerWithdrawalEntity.find {
+ TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
+ }.firstOrNull() ?: throw SandboxError(
+ HttpStatusCode.NotFound,
+ "Withdrawal operation: $wopid not found"
+ )
+ }
+ val demobank =
ensureDemobank(call.getUriComponent("demobankid"))
+ val ret = TalerWithdrawalStatus(
+ selection_done = wo.selectionDone,
+ transfer_done = wo.transferDone,
+ amount = wo.amount,
+ suggested_exchange = demobank.suggestedExchange
+ )
+ call.respond(ret)
+ return@get
+ }
+ }
+ // Talk to Web UI.
route("/access-api") {
-
+ // Create a new withdrawal operation.
post("/accounts/{account_name}/withdrawals") {
val username = call.request.basicAuth()
- ensureDemobank(call.getUriComponent("demobankid"))
+ if (username == null) throw badRequest(
+ "Taler withdrawal tried with authentication disabled.
" +
+ "That is impossible, because no bank account
can get this operation debited."
+ )
+ val demobank =
ensureDemobank(call.getUriComponent("demobankid"))
/**
- * Check that the three canonical accounts exist. The
names
- * below match those used in the testing harnesses.
+ * 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 wo: TalerWithdrawalEntity = transaction {
- val exchange = BankAccountEntity.find {
- BankAccountsTable.label eq
"sandbox-account-exchange"
- }.firstOrNull()
- val customer = BankAccountEntity.find {
- BankAccountsTable.label eq
"sandbox-account-customer"
- }.firstOrNull()
- val merchant = BankAccountEntity.find {
- BankAccountsTable.label eq
"sandbox-account-merchant"
- }.firstOrNull()
- SandboxAssert(exchange != null, "exchange has no bank
account")
- SandboxAssert(customer != null, "customer has no bank
account")
- SandboxAssert(merchant != null, "merchant has no bank
account")
- // At this point, the three actors exist and a new
withdraw operation can be created.
- val wo = TalerWithdrawalEntity.new { /* wopid is
autogenerated, and momentarily the only column */ }
- wo
- }
+ val maybeOwnedAccount =
getBankAccountFromLabel(call.getUriComponent("account_name"))
+ if (maybeOwnedAccount.owner != username) throw
unauthorized(
+ "Customer '$username' has no rights over bank account
'${maybeOwnedAccount.label}'"
+ )
+ val wo: TalerWithdrawalEntity = transaction {
TalerWithdrawalEntity.new {
+ amount = "${demobank.currency}:5"
+ walletBankAccount = maybeOwnedAccount
+ } }
val baseUrl = URL(call.request.getBaseUrl())
val withdrawUri = call.url {
protocol = URLProtocol(
"taler".plus(if (baseUrl.protocol.lowercase() ==
"http") "+http" else ""),
-1
)
- pathComponents(baseUrl.path, "api",
wo.wopid.toString())
+ pathComponents(baseUrl.path, "access-api",
wo.wopid.toString())
encodedPath += "/"
}
call.respond(object {
@@ -1070,13 +1043,48 @@ val sandboxApp: Application.() -> Unit = {
})
return@post
}
-
- // Confirm the wire transfer to the exchange. Idempotent
- post("/accounts/{account_name}/withdrawals/confirm") {
-
-
+ // Confirm a withdrawal.
+
post("/accounts/{account_name}/withdrawals/{withdrawal_id}/confirm") {
+ val withdrawalId = call.getUriComponent("withdrawal_id")
+ transaction {
+ val wo = TalerWithdrawalEntity.find {
+ TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(withdrawalId)
+ }.firstOrNull() ?: throw SandboxError(
+ HttpStatusCode.NotFound, "Withdrawal operation
$withdrawalId not found."
+ )
+ if (wo.aborted) throw SandboxError(
+ HttpStatusCode.Conflict,
+ "Cannot confirm an aborted withdrawal."
+ )
+ if (!wo.selectionDone) throw SandboxError(
+ HttpStatusCode.UnprocessableEntity,
+ "Cannot confirm a unselected withdrawal: " +
+ "specify exchange and reserve public key
via Integration API first."
+ )
+ val exchangeBankAccount = getBankAccountFromPayto(
+ wo.selectedExchangePayto ?: throw
internalServerError(
+ "Cannot withdraw without an exchange."
+ )
+ )
+ if (!wo.transferDone) {
+ // Need the exchange bank account!
+ wireTransfer(
+ debitAccount = wo.walletBankAccount,
+ creditAccount = exchangeBankAccount,
+ amount = wo.amount,
+ subject = wo.reservePub ?: throw
internalServerError(
+ "Cannot transfer funds without reserve
public key."
+ ),
+ demoBank =
ensureDemobank(call.getUriComponent("demobankid"))
+ )
+ wo.transferDone = true
+ }
+ wo.transferDone
+ }
+ call.respond(object {})
return@post
}
+ // Bank account basic information.
get("/accounts/{account_name}") {
val username = call.request.basicAuth()
val accountAccessed = call.getUriComponent("account_name")
@@ -1086,17 +1094,12 @@ val sandboxApp: Application.() -> Unit = {
}.firstOrNull()
res
} ?: throw notFound("Account '$accountAccessed' not found")
-
// Check rights.
if (WITH_AUTH) {
- val customer = getCustomerFromDb(username ?: throw
internalServerError(
- "Optional authentication broken!"
- ))
- if (customer.bankAccount.label != accountAccessed)
throw forbidden(
+ if (bankAccount.owner != username) throw forbidden(
"Customer '$username' cannot access bank account
'$accountAccessed'"
)
}
-
val creditDebitIndicator = if (bankAccount.isDebit) {
"debit"
} else {
@@ -1110,33 +1113,25 @@ val sandboxApp: Application.() -> Unit = {
})
return@get
}
-
get("/accounts/{account_name}/history") {
// New endpoint, access account history to display in the
SPA
// (could be merged with GET /accounts/{account_name}
}
-
- // [...]
-
- get("/public-accounts") {
+ get("/accounts/public") {
val demobank =
ensureDemobank(call.getUriComponent("demobankid"))
val ret = object {
- val publicAccounts = mutableListOf<CustomerInfo>()
+ val publicAccounts = mutableListOf<PublicAccountInfo>()
}
transaction {
- DemobankCustomerEntity.find {
- DemobankCustomersTable.isPublic eq true and(
- DemobankCustomersTable.demobankConfig eq
demobank.id
+ BankAccountEntity.find {
+ BankAccountsTable.isPublic eq true and(
+ BankAccountsTable.demoBank eq demobank.id
)
}.forEach {
ret.publicAccounts.add(
- CustomerInfo(
- username = it.username,
- balance = it.bankAccount.balance,
- iban = it.bankAccount.iban,
- name = it.name ?: throw
internalServerError(
- "Found name-less public account,
username: ${it.username}"
- )
+ PublicAccountInfo(
+ balance = it.balance,
+ iban = it.iban
)
)
}
@@ -1145,11 +1140,11 @@ val sandboxApp: Application.() -> Unit = {
return@get
}
- get("/public-accounts/{account_name}/history") {
+ get("/accounts/public/{account_name}/history") {
// Get transaction history of a public account
}
- // Keeping the prefix "testing" to allow integration tests
using this endpoint.
+ // Keeping the prefix "testing" not to break tests.
post("/testing/register") {
// Check demobank was created.
val demobank =
ensureDemobank(call.getUriComponent("demobankid"))
@@ -1173,12 +1168,12 @@ val sandboxApp: Application.() -> Unit = {
label = req.username + "acct" // multiple accounts
per username not allowed.
currency = demobank.currency
balance = "${demobank.currency}:0"
+ owner = req.username
}
DemobankCustomerEntity.new {
username = req.username
passwordHash = CryptoUtil.hashpw(req.password)
demobankConfig = demobank
- this.bankAccount = bankAccount
}
}
call.respondText("Registration successful")
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
index 151af01..ea9b5b1 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -12,17 +12,6 @@ import java.math.BigDecimal
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-fun getAccountFromLabel(accountLabel: String): BankAccountEntity {
- return transaction {
- val account = BankAccountEntity.find {
- BankAccountsTable.label eq accountLabel
- }.firstOrNull()
- if (account == null) throw SandboxError(
- HttpStatusCode.NotFound, "Account '$accountLabel' not found"
- )
- account
- }
-}
// Mainly useful inside the CAMT generator.
fun balanceForAccount(
history: MutableList<RawPayment>,
@@ -120,10 +109,14 @@ fun historyForAccount(bankAccount: BankAccountEntity):
MutableList<RawPayment> {
/**
*
https://github.com/JetBrains/Exposed/wiki/Transactions#working-with-coroutines
*
https://medium.com/androiddevelopers/threading-models-in-coroutines-and-android-sqlite-api-6cab11f7eb90
+ *
+ * FIXME: This version will be deprecated. It was made before introducing the
demobank configuration
*/
fun wireTransfer(
- debitAccount: String, creditAccount: String,
- amount: String, subjectArg: String
+ debitAccount: String,
+ creditAccount: String,
+ amount: String,
+ subjectArg: String
) {
transaction {
// check accounts exist
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index e835f08..9ebf072 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -8,7 +8,7 @@ import io.ktor.util.*
import logger
import java.net.URLDecoder
-private fun unauthorized(msg: String): UtilError {
+fun unauthorized(msg: String): UtilError {
return UtilError(
HttpStatusCode.Unauthorized,
msg,
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
index 2656efc..bbd8fb6 100644
--- a/util/src/main/kotlin/JSON.kt
+++ b/util/src/main/kotlin/JSON.kt
@@ -23,6 +23,8 @@ package tech.libeufin.util
* (Very) generic information about one payment. Can be
* derived from a CAMT response, or from a prepared PAIN
* document.
+ *
+ * Note:
*/
data class RawPayment(
val creditorIban: String,
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt
index f6e1ca8..1c10263 100644
--- a/util/src/main/kotlin/Payto.kt
+++ b/util/src/main/kotlin/Payto.kt
@@ -6,6 +6,7 @@ import java.net.URI
* Helper data structures.
*/
data class Payto(
+ // Can represent a the sender or a receiver.
val name: String?,
val iban: String,
val bic: String?
@@ -23,7 +24,10 @@ fun parsePayto(paytoLine: String): Payto {
if (javaParsedUri.scheme != "payto") {
throw InvalidPaytoError("'${paytoLine}' is not payto")
}
-
+ val wireMethod = javaParsedUri.host
+ if (wireMethod != "sepa") {
+ throw InvalidPaytoError("Only SEPA is supported, not '$wireMethod'")
+ }
val accountOwner = if (javaParsedUri.query != null) {
val queryStringAsList = javaParsedUri.query.split("&")
// admit only ONE parameter: receiver-name.
--
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: Group Integration/Access API under Demobank route.,
gnunet <=