gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

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