gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (a6661dff -> fe229973)


From: gnunet
Subject: [libeufin] branch master updated (a6661dff -> fe229973)
Date: Wed, 10 Nov 2021 16:22:43 +0100

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

ms pushed a change to branch master
in repository libeufin.

    from a6661dff allow 'admin' to access other customers accounts
     new cdc8f9b1 Finish basic auth implementation.
     new b4afc11a fix visibility issue
     new eda34712 adapt after wallet tests harness
     new f255d688 Fixes after wallet tests harness.
     new 8643676e fix exchange suggestion
     new c794115e allow longer currency names
     new 145bfdbd fixes after wallet harness
     new fe229973 Fixes after wallet tests harness.

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 access-api-stash/AccessApiNexus.kt                 | 210 +++++++++++++++++++++
 .../src/main/kotlin/tech/libeufin/sandbox/DB.kt    |   8 +-
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  |   2 -
 .../main/kotlin/tech/libeufin/sandbox/Helpers.kt   |  76 ++++++--
 .../src/main/kotlin/tech/libeufin/sandbox/JSON.kt  |   2 +-
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 132 +++++++++----
 .../kotlin/tech/libeufin/sandbox/bankAccount.kt    |   2 -
 util/src/main/kotlin/HTTP.kt                       |  33 +---
 util/src/main/kotlin/JSON.kt                       |   6 +
 util/src/main/kotlin/Payto.kt                      |   4 +-
 util/src/main/kotlin/iban.kt                       |  15 +-
 util/src/main/resources/xsd/camt.052.001.02.xsd    |   2 +-
 util/src/main/resources/xsd/camt.053.001.02.xsd    |   2 +-
 util/src/main/resources/xsd/camt.054.001.02.xsd    |   2 +-
 util/src/test/kotlin/ibanTest.kt                   |  10 +
 15 files changed, 405 insertions(+), 101 deletions(-)
 create mode 100644 access-api-stash/AccessApiNexus.kt
 create mode 100644 util/src/test/kotlin/ibanTest.kt

diff --git a/access-api-stash/AccessApiNexus.kt 
b/access-api-stash/AccessApiNexus.kt
new file mode 100644
index 00000000..18347083
--- /dev/null
+++ b/access-api-stash/AccessApiNexus.kt
@@ -0,0 +1,210 @@
+package tech.libeufin.nexus.`access-api`
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import io.ktor.client.*
+import io.ktor.client.features.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.util.*
+import org.jetbrains.exposed.sql.not
+import org.jetbrains.exposed.sql.transactions.transaction
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.server.AccessApiNewTransport
+import tech.libeufin.nexus.server.EbicsNewTransport
+import tech.libeufin.nexus.server.FetchSpecJson
+import tech.libeufin.nexus.server.client
+import tech.libeufin.util.*
+import java.net.URL
+import java.nio.charset.Charset
+
+private fun getAccessApiClient(connId: String): AccessApiClientEntity {
+    val conn = NexusBankConnectionEntity.find {
+        NexusBankConnectionsTable.connectionId eq connId
+    }.firstOrNull() ?: throw notFound("Connection '$connId' not found.")
+    val client = AccessApiClientEntity.find {
+        AccessApiClientsTable.nexusBankConnection eq conn.id.value
+    }.firstOrNull() ?: throw notFound("Connection '$connId' has no client 
data.")
+    return client
+}
+
+suspend fun HttpClient.accessApiReq(
+    method: HttpMethod,
+    url: String,
+    body: Any? = null,
+    // username, password
+    credentials: Pair<String, String>): String? {
+
+    val reqBuilder: HttpRequestBuilder.() -> Unit = {
+        contentType(ContentType.Application.Json)
+        if (body != null)
+            this.body = body
+
+        headers.apply {
+            this.set(
+                "Authorization",
+                "Basic " + 
bytesToBase64("${credentials.first}:${credentials.second}".toByteArray(Charsets.UTF_8))
+            )
+        }
+    }
+    return try {
+        when(method) {
+            HttpMethod.Get -> {
+                this.get(url, reqBuilder)
+            }
+            HttpMethod.Post -> {
+                this.post(url, reqBuilder)
+            }
+            else -> throw internalServerError("Method $method not supported.")
+        }
+    } catch (e: ClientRequestException) {
+        logger.error(e.message)
+        throw NexusError(
+            HttpStatusCode.BadGateway,
+            e.message
+        )
+    }
+}
+
+/**
+ * Talk to the Sandbox via native Access API.  The main reason
+ * for this class was to still allow x-taler-bank as a wire method
+ * (to accommodate wallet harness tests), and therefore skip the
+ * Camt+Pain schemas.
+ */
+class JsonBankConnectionProtocol: BankConnectionProtocol {
+
+    override fun createConnection(connId: String, user: NexusUserEntity, data: 
JsonNode) {
+        val bankConn = NexusBankConnectionEntity.new {
+            this.connectionId = connId
+            owner = user
+            type = "access-api"
+        }
+        val newTransportData = jacksonObjectMapper(
+        ).treeToValue(data, AccessApiNewTransport::class.java) ?: throw 
NexusError(
+            HttpStatusCode.BadRequest, "Access Api details not found in 
request"
+        )
+        AccessApiClientEntity.new {
+            username = newTransportData.username
+            bankURL = newTransportData.bankURL
+            remoteBankAccountLabel = newTransportData.remoteBankAccountLabel
+            nexusBankConnection = bankConn
+            password = newTransportData.password
+        }
+    }
+
+    override fun getConnectionDetails(conn: NexusBankConnectionEntity): 
JsonNode {
+        val details = transaction { getAccessApiClient(conn.connectionId) }
+        val ret = ObjectMapper().createObjectNode()
+        ret.put("username", details.username)
+        ret.put("bankURL", details.bankURL)
+        ret.put("passwordHash", CryptoUtil.hashpw(details.password))
+        ret.put("remoteBankAccountLabel", details.remoteBankAccountLabel)
+        return ret
+    }
+
+    override suspend fun submitPaymentInitiation(
+        httpClient: HttpClient,
+        paymentInitiationId: Long // must refer to an x-taler-bank 
payto://-instruction.
+    ) {
+        val payInit = 
XTalerBankPaymentInitiationEntity.findById(paymentInitiationId) ?: throw 
notFound(
+            "Payment initiation '$paymentInitiationId' not found."
+        )
+        val conn = payInit.defaultBankConnection ?: throw notFound(
+            "No default bank connection for payment initiation 
'${paymentInitiationId}' was found."
+        )
+        val details = getAccessApiClient(conn.connectionId)
+
+        client.accessApiReq(
+            method = HttpMethod.Post,
+            url = urlJoinNoDrop(
+                details.bankURL,
+                "accounts/${details.remoteBankAccountLabel}/transactions"
+            ),
+            body = object {
+                val paytoUri = payInit.paytoUri
+                val amount = payInit.amount
+                val subject = payInit.subject
+            },
+            credentials = Pair(details.username, details.password)
+        )
+    }
+    /**
+     * This function gets always the fresh transactions from
+     * the bank.  Any other Wire Gateway API policies will be
+     * implemented by the respective facade (XTalerBank.kt) */
+    override suspend fun fetchTransactions(
+        fetchSpec: FetchSpecJson,
+        client: HttpClient,
+        bankConnectionId: String,
+        /**
+         * Label of the local bank account that mirrors
+         * the remote bank account pointed to by 'bankConnectionId' */
+        accountId: String
+    ) {
+        val details = getAccessApiClient(bankConnectionId)
+        val txsRaw = client.accessApiReq(
+            method = HttpMethod.Get,
+            url = urlJoinNoDrop(
+                details.bankURL,
+                "accounts/${details.remoteBankAccountLabel}/transactions"
+            ),
+            credentials = Pair(details.username, details.password)
+        )
+        // What format does Access API communicates the records in?
+        /**
+         * NexusXTalerBankTransactions.new {
+         *
+         *     .. details ..
+         * }
+         */
+    }
+
+    override fun exportBackup(bankConnectionId: String, passphrase: String): 
JsonNode {
+        throw NexusError(
+            HttpStatusCode.NotImplemented,
+            "Operation not needed."
+        )
+    }
+
+    override fun exportAnalogDetails(conn: NexusBankConnectionEntity): 
ByteArray {
+        throw NexusError(
+            HttpStatusCode.NotImplemented,
+            "Operation not needed."
+        )
+    }
+
+    override suspend fun fetchAccounts(client: HttpClient, connId: String) {
+        throw NexusError(
+            HttpStatusCode.NotImplemented,
+            "access-api connections assume that remote and local bank" +
+                    " accounts are called the same.  No need to 'fetch'"
+        )
+    }
+
+    override fun createConnectionFromBackup(
+        connId: String,
+        user: NexusUserEntity,
+        passphrase: String?,
+        backup: JsonNode
+    ) {
+        throw NexusError(
+            HttpStatusCode.NotImplemented,
+            "Operation not needed."
+        )
+    }
+
+    override suspend fun connect(client: HttpClient, connId: String) {
+        /**
+         * Future versions might create a bank account at this step.
+         * Right now, all the tests do create those accounts beforehand.
+         */
+        throw NexusError(
+            HttpStatusCode.NotImplemented,
+            "Operation not needed."
+        )
+    }
+}
+
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index b9863704..e5f6a695 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -95,7 +95,8 @@ object DemobankConfigsTable : LongIdTable() {
     val bankDebtLimit = integer("bankDebtLimit")
     val usersDebtLimit = integer("usersDebtLimit")
     val name = text("hostname")
-    val suggestedExchange = text("suggestedExchange").nullable()
+    val suggestedExchangeBaseUrl = text("suggestedExchangeBaseUrl").nullable()
+    val suggestedExchangePayto = text("suggestedExchangePayto").nullable()
 }
 
 class DemobankConfigEntity(id: EntityID<Long>) : LongEntity(id) {
@@ -105,7 +106,8 @@ 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
+    var suggestedExchangeBaseUrl by 
DemobankConfigsTable.suggestedExchangeBaseUrl
+    var suggestedExchangePayto by DemobankConfigsTable.suggestedExchangePayto
 }
 
 /**
@@ -366,7 +368,7 @@ class BankAccountTransactionEntity(id: EntityID<Long>) : 
LongEntity(id) {
  */
 object BankAccountsTable : IntIdTable() {
     val iban = text("iban")
-    val bic = text("bic").default("EUSANDBOX")
+    val bic = text("bic").default("SANDBOXX")
     val label = text("label").uniqueIndex("accountLabelIndex")
     val isDebit = bool("isDebit").default(false)
     /**
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index c719064f..2a819a7a 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -54,8 +54,6 @@ import java.util.zip.InflaterInputStream
 
 val EbicsHostIdAttribute = AttributeKey<String>("RequestedEbicsHostID")
 
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-
 data class PainParseResult(
     val creditorIban: String,
     val creditorName: String,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index 9005fd0f..2e927000 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -21,6 +21,7 @@ package tech.libeufin.sandbox
 
 import io.ktor.application.*
 import io.ktor.http.HttpStatusCode
+import io.ktor.request.*
 import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
@@ -40,6 +41,42 @@ data class SandboxCamt(
     val creationTime: Long
 )
 
+/**
+ * Return:
+ * - null if the authentication is disabled (during tests, for example).
+ *   This facilitates tests because allows requests to lack entirely a
+ *   Authorization header.
+ * - the name of the authenticated user
+ * - throw exception when the authentication fails
+ *
+ * Note: at this point it is ONLY checked whether the user provided
+ * a valid password for the username mentioned in the Authorization header.
+ * The actual access to the resources must be later checked by each handler.
+ */
+fun ApplicationRequest.basicAuth(): String? {
+    val withAuth = this.call.ensureAttribute(WITH_AUTH_ATTRIBUTE_KEY)
+    if (!withAuth) {
+        logger.info("Authentication is disabled - assuming tests currently 
running.")
+        return null
+    }
+    val credentials = getHTTPBasicAuthCredentials(this)
+    if (credentials.first == "admin") {
+        // env must contain the admin password, because --with-auth is true.
+        val adminPassword: String = 
this.call.ensureAttribute(ADMIN_PASSWORD_ATTRIBUTE_KEY)
+        if (credentials.second != adminPassword) throw unauthorized(
+            "Admin authentication failed"
+        )
+        return credentials.first
+    }
+    val passwordHash = transaction {
+        val customer = getCustomer(credentials.first)
+        customer.passwordHash
+    }
+    if (!CryptoUtil.checkPwOrThrow(credentials.second, passwordHash))
+        throw unauthorized("Customer '${credentials.first}' gave wrong 
credentials")
+    return credentials.first
+}
+
 fun SandboxAssert(condition: Boolean, reason: String) {
     if (!condition) throw SandboxError(HttpStatusCode.InternalServerError, 
reason)
 }
@@ -85,17 +122,26 @@ fun getHistoryElementFromTransactionRow(
     return getHistoryElementFromTransactionRow(dbRow.transactionRef)
 }
 
+// Need to be called within a transaction {} block.
+fun getCustomer(username: String): DemobankCustomerEntity {
+    return DemobankCustomerEntity.find {
+        DemobankCustomersTable.username eq username
+    }.firstOrNull() ?: throw notFound("Customer '${username}' not found")
+}
+
 /**
  * Get person name from a customer's username.
  */
 fun getPersonNameFromCustomer(ownerUsername: String): String {
     return if (ownerUsername == "admin") "admin" else {
-        val ownerCustomer = DemobankCustomerEntity.find(
-            DemobankCustomersTable.username eq ownerUsername
-        ).firstOrNull() ?: throw internalServerError(
-            "Person name of '$ownerUsername' not found"
-        )
-        ownerCustomer.name ?: "Name not given"
+        return transaction {
+            val ownerCustomer = DemobankCustomerEntity.find(
+                DemobankCustomersTable.username eq ownerUsername
+            ).firstOrNull() ?: throw internalServerError(
+                "Person name of '$ownerUsername' not found"
+            )
+            ownerCustomer.name ?: "Name not given"
+        }
     }
 }
 fun getDefaultDemobank(): DemobankConfigEntity {
@@ -130,13 +176,13 @@ fun wireTransfer(
     amount: String // $currency:x.y
 ): String {
     val args: Triple<BankAccountEntity, BankAccountEntity, 
DemobankConfigEntity> = transaction {
-        val debitAccount = BankAccountEntity.find {
+        val debitAccountDb = BankAccountEntity.find {
             BankAccountsTable.label eq debitAccount
         }.firstOrNull() ?: throw SandboxError(
             HttpStatusCode.NotFound,
             "Debit account '$debitAccount' not found"
         )
-        val creditAccount = BankAccountEntity.find {
+        val creditAccountDb = BankAccountEntity.find {
             BankAccountsTable.label eq creditAccount
         }.firstOrNull() ?: throw SandboxError(
             HttpStatusCode.NotFound,
@@ -149,7 +195,7 @@ fun wireTransfer(
             "Demobank '$demobank' not found"
         )
 
-        Triple(debitAccount, creditAccount, demoBank)
+        Triple(debitAccountDb, creditAccountDb, demoBank)
     }
 
     /**
@@ -182,7 +228,8 @@ fun wireTransfer(
     subject: String,
     amount: String,
 ): String {
-
+    // sanity check on the amount, no currency allowed here.
+    parseDecimal(amount)
     val timeStamp = getUTCnow().toInstant().toEpochMilli()
     val transactionRef = getRandomString(8)
     transaction {
@@ -235,7 +282,6 @@ fun getWithdrawalOperation(opId: String): 
TalerWithdrawalEntity {
 fun getBankAccountFromPayto(paytoUri: String): BankAccountEntity {
     val paytoParse = parsePayto(paytoUri)
     return getBankAccountFromIban(paytoParse.iban)
-
 }
 
 fun getBankAccountFromIban(iban: String): BankAccountEntity {
@@ -259,11 +305,11 @@ fun getBankAccountFromLabel(label: String, demobank: 
DemobankConfigEntity): Bank
     return transaction {
         BankAccountEntity.find(
             BankAccountsTable.label eq label and (BankAccountsTable.demoBank 
eq demobank.id)
+        ).firstOrNull() ?: throw SandboxError(
+            HttpStatusCode.NotFound,
+            "Did not find a bank account for label ${label}"
         )
-    }.firstOrNull() ?: throw SandboxError(
-        HttpStatusCode.NotFound,
-        "Did not find a bank account for label ${label}"
-    )
+    }
 }
 
 fun getBankAccountFromSubscriber(subscriber: EbicsSubscriberEntity): 
BankAccountEntity {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index b25741e6..1da00738 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -120,7 +120,7 @@ data class TalerWithdrawalStatus(
     val selection_done: Boolean,
     val transfer_done: Boolean,
     val amount: String,
-    val wire_types: List<String> = listOf("sepa"),
+    val wire_types: List<String> = listOf("iban"),
     val suggested_exchange: String? = null,
     val sender_wire: String? = null,
     val aborted: Boolean = false,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 6ddd9152..2e405b40 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -80,7 +80,7 @@ import java.security.interfaces.RSAPublicKey
 import javax.xml.bind.JAXBContext
 import kotlin.system.exitProcess
 
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
 private val currencyEnv: String? = System.getenv("LIBEUFIN_SANDBOX_CURRENCY")
 const val SANDBOX_DB_ENV_VAR_NAME = "LIBEUFIN_SANDBOX_DB_CONNECTION"
 private val adminPassword: String? = 
System.getenv("LIBEUFIN_SANDBOX_ADMIN_PASSWORD")
@@ -101,7 +101,8 @@ class DefaultExchange : CliktCommand("Set default Taler 
exchange for a demobank.
             helpFormatter = CliktHelpFormatter(showDefaultValues = true)
         }
     }
-    private val exchange by argument("EXCHANGE", "Payto URI of the default 
exchange")
+    private val exchangeBaseUrl by argument("EXCHANGE-BASEURL", "base URL of 
the default exchange")
+    private val exchangePayto by argument("EXCHANGE-PAYTO", "default 
exchange's payto-address")
     private val demobank by option("--demobank", help = "Which demobank 
defaults to EXCHANGE").default("default")
 
     override fun run() {
@@ -116,7 +117,8 @@ class DefaultExchange : CliktCommand("Set default Taler 
exchange for a demobank.
                     println("Error, demobank ${demobank} not found.")
                     exitProcess(1)
                 }
-                maybeDemobank.suggestedExchange = exchange
+                maybeDemobank.suggestedExchangeBaseUrl = exchangeBaseUrl
+                maybeDemobank.suggestedExchangePayto = exchangePayto
             }
         }
     }
@@ -951,20 +953,6 @@ val sandboxApp: Application.() -> Unit = {
             return@post
         }
 
-        get("/api/config") {
-            SandboxAssert(
-                currencyEnv != null,
-                "Currency not found.  Logs should have warned"
-            )
-            call.respond(object {
-                val name = "taler-bank-integration"
-
-                // FIXME: use actual version here!
-                val version = "0:0:0"
-                val currency = currencyEnv
-            })
-        }
-
         // Create a new demobank instance with a particular currency,
         // debt limit and possibly other configuration
         // (could also be a CLI command for now)
@@ -998,9 +986,53 @@ val sandboxApp: Application.() -> Unit = {
 
         route("/demobanks/{demobankid}") {
 
+            // NOTE: TWG assumes that username == bank account label.
+            route("/taler-wire-gateway") {
+                post("/{exchangeUsername}/admin/add-incoming") {
+                    val username = call.getUriComponent("exchangeUsername")
+                    val usernameAuth = call.request.basicAuth()
+                    if (username != usernameAuth) {
+                        throw forbidden(
+                            "Bank account name and username differ: $username 
vs $usernameAuth"
+                        )
+                    }
+                    logger.debug("TWG add-incoming passed authentication")
+                    val body = call.receive<TWGAdminAddIncoming>()
+                    transaction {
+                        val demobank = ensureDemobank(call)
+                        val bankAccountCredit = 
getBankAccountFromLabel(username, demobank)
+                        if (bankAccountCredit.owner != username) throw 
forbidden(
+                            "User '$username' cannot access bank account with 
label: $username."
+                        )
+                        val bankAccountDebit = 
getBankAccountFromPayto(body.debit_account)
+                        logger.debug("TWG add-incoming about to wire transfer")
+                        wireTransfer(
+                            bankAccountDebit.label,
+                            bankAccountCredit.label,
+                            demobank.name,
+                            body.reserve_pub,
+                            body.amount
+                        )
+                        logger.debug("TWG add-incoming has wire transferred")
+                    }
+                    call.respond(object {})
+                    return@post
+                }
+            }
             // Talk to wallets.
             route("/integration-api") {
-                post("/api/withdrawal-operation/{wopid}") {
+
+                get("/config") {
+                    val demobank = ensureDemobank(call)
+                    call.respond(object {
+                        val name = "taler-bank-integration"
+                        // FIXME: avoid hard-coding the version!
+                        val version = "0:0:0"
+                        val currency = demobank.currency
+                    })
+                    return@get
+                }
+                post("/withdrawal-operation/{wopid}") {
                     val wopid: String = ensureNonNull(call.parameters["wopid"])
                     val body = call.receiveJson<TalerWithdrawalSelection>()
                     val transferDone = newSuspendedTransaction(context = 
singleThreadContext) {
@@ -1012,7 +1044,7 @@ val sandboxApp: Application.() -> Unit = {
                         if (wo.selectionDone) {
                             if (wo.confirmationDone) {
                                 logger.info("Wallet performs again this 
operation that was paid out earlier: idempotent")
-                                return@newSuspendedTransaction
+                                return@newSuspendedTransaction wo.selectionDone
                             }
                             // Selected already but NOT paid, check 
consistency.
                             if (body.reserve_pub != wo.reservePub) throw 
SandboxError(
@@ -1028,9 +1060,10 @@ val sandboxApp: Application.() -> Unit = {
                         wo.selectedExchangePayto = body.selected_exchange
                         wo.selectionDone = true
                         wo.confirmationDone
+                        false // not confirmed (AKA transferred)
                     }
                     call.respond(object {
-                        val transfer_done = transferDone
+                        val transfer_done: Boolean = transferDone
                     })
                     return@post
                 }
@@ -1048,8 +1081,8 @@ val sandboxApp: Application.() -> Unit = {
                     val ret = TalerWithdrawalStatus(
                         selection_done = wo.selectionDone,
                         transfer_done = wo.confirmationDone,
-                        amount = wo.amount,
-                        suggested_exchange = demobank.suggestedExchange
+                        amount = "${demobank.currency}:${wo.amount}",
+                        suggested_exchange = demobank.suggestedExchangeBaseUrl
                     )
                     call.respond(ret)
                     return@get
@@ -1086,8 +1119,7 @@ val sandboxApp: Application.() -> Unit = {
                     /**
                      * 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.
-                     */
+                     * UID. */
                     val maybeOwnedAccount = getBankAccountFromLabel(
                         call.getUriComponent("account_name"),
                         demobank
@@ -1101,21 +1133,37 @@ val sandboxApp: Application.() -> Unit = {
                     if (amount.currency != demobank.currency) throw badRequest(
                         "Currency ${amount.currency} differs from Demobank's: 
${demobank.currency}"
                     )
-                    val wo: TalerWithdrawalEntity = transaction { 
TalerWithdrawalEntity.new {
+                    val wo: TalerWithdrawalEntity = transaction {
+                        TalerWithdrawalEntity.new {
                         this.amount = amount.amount.toPlainString()
                         walletBankAccount = maybeOwnedAccount
-                    } }
+                        }
+                    }
                     val baseUrl = URL(call.request.getBaseUrl())
-                    val withdrawUri = call.url {
+                    val withdrawUri = url {
                         protocol = URLProtocol(
                             "taler".plus(if (baseUrl.protocol.lowercase() == 
"http") "+http" else ""),
                             -1
                         )
-                        pathComponents(baseUrl.path, "access-api", 
wo.wopid.toString())
-                        encodedPath += "/"
+                        host = "withdraw"
+                        pathComponents(
+                            /**
+                             * encodes the hostname(+port) of the actual
+                             * bank that will serve the withdrawal request.
+                             */
+                            baseUrl.host.plus(
+                                if (baseUrl.port != -1)
+                                    ":${baseUrl.port}"
+                                else ""
+                            ),
+                            "demobanks",
+                            demobank.name,
+                            "integration-api",
+                            wo.wopid.toString()
+                        )
                     }
                     call.respond(object {
-                        val withdrawal_id = wo.id.value
+                        val withdrawal_id = wo.wopid.toString()
                         val taler_withdraw_uri = withdrawUri
                     })
                     return@post
@@ -1268,10 +1316,15 @@ val sandboxApp: Application.() -> Unit = {
                     }
                     // Create new customer.
                     requireValidResourceName(req.username)
-                    transaction {
-                        BankAccountEntity.new {
+                    val bankAccount = transaction {
+                        val bankAccount = BankAccountEntity.new {
                             iban = getIban()
-                            label = req.username + "-acct" // multiple 
accounts per username not allowed.
+                            /**
+                             * For now, keep same semantics of Pybank: a 
username
+                             * is AS WELL a bank account label.  In other 
words, it
+                             * identifies a customer AND a bank account.
+                             */
+                            label = req.username
                             owner = req.username
                             this.demoBank = demobank
                         }
@@ -1279,8 +1332,19 @@ val sandboxApp: Application.() -> Unit = {
                             username = req.username
                             passwordHash = CryptoUtil.hashpw(req.password)
                         }
+                        bankAccount
                     }
-                    call.respond(object {})
+                    call.respond(object {
+                        val balance = {
+                            val amount = "${demobank.currency}:0"
+                            val credit_debit_indicator = "CRDT"
+                        }
+                        val paytoUri = buildIbanPaytoUri(
+                            iban = bankAccount.iban,
+                            bic = bankAccount.bic,
+                            receiverName = 
getPersonNameFromCustomer(req.username)
+                        )
+                    })
                     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 8c04c7b3..b775bac2 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/bankAccount.kt
@@ -10,8 +10,6 @@ import org.slf4j.LoggerFactory
 import tech.libeufin.util.*
 import java.math.BigDecimal
 
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-
 // Mainly useful inside the CAMT generator.
 fun balanceForAccount(
     history: MutableList<RawPayment>,
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index 70f66316..26982601 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -6,6 +6,7 @@ import io.ktor.http.*
 import io.ktor.request.*
 import io.ktor.util.*
 import logger
+import org.jetbrains.exposed.sql.transactions.transaction
 import java.net.URLDecoder
 
 fun unauthorized(msg: String): UtilError {
@@ -117,38 +118,6 @@ fun ApplicationCall.getUriComponent(name: String): String {
     if (ret == null) throw internalServerError("Component $name not found in 
URI")
     return ret
 }
-/**
- * Return:
- * - null if the authentication is disabled (during tests, for example).
- *   This facilitates tests because allows requests to lack entirely a
- *   Authorization header.
- * - the name of the authenticated user
- * - throw exception when the authentication fails
- *
- * Note: at this point it is ONLY checked whether the user provided
- * a valid password for the username mentioned in the Authorization header.
- * The actual access to the resources must be later checked by each handler.
- */
-fun ApplicationRequest.basicAuth(): String? {
-    val withAuth = this.call.ensureAttribute(WITH_AUTH_ATTRIBUTE_KEY)
-    if (!withAuth) {
-        logger.info("Authentication is disabled - assuming tests currently 
running.")
-        return null
-    }
-    val credentials = getHTTPBasicAuthCredentials(this)
-    if (credentials.first == "admin") {
-        // env must contain the admin password, because --with-auth is true.
-        val adminPassword: String = 
this.call.ensureAttribute(ADMIN_PASSWORD_ATTRIBUTE_KEY)
-        if (credentials.second != adminPassword) throw unauthorized(
-            "Admin authentication failed"
-        )
-        return credentials.first
-    }
-    throw unauthorized("Demobank customers not implemented yet!")
-    /**
-     * TODO: extract customer hashed password from the database and check.
-     */
-}
 
 /**
  * Throw "unauthorized" if the request is not
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
index ee36b37e..c4a97fcb 100644
--- a/util/src/main/kotlin/JSON.kt
+++ b/util/src/main/kotlin/JSON.kt
@@ -59,6 +59,12 @@ data class IncomingPaymentInfo(
     val subject: String
 )
 
+data class TWGAdminAddIncoming(
+    val amount: String,
+    val reserve_pub: String,
+    val debit_account: String
+)
+
 data class PaymentInfo(
     val accountLabel: String,
     val creditorIban: String,
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt
index 2308a25e..dcf9c874 100644
--- a/util/src/main/kotlin/Payto.kt
+++ b/util/src/main/kotlin/Payto.kt
@@ -74,9 +74,9 @@ fun parsePayto(paytoLine: String): Payto {
 
 fun buildIbanPaytoUri(
     iban: String,
-    bic: String,
+    bic: String?,
     receiverName: String,
 ): String {
     val nameUrlEnc = URLEncoder.encode(receiverName, "utf-8")
-    return "payto://iban/$bic/$iban?receiver-name=$nameUrlEnc"
+    return "payto://iban/${if (bic != null) "$bic/" else 
""}$iban?receiver-name=$nameUrlEnc"
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/iban.kt b/util/src/main/kotlin/iban.kt
index 4b8055da..cf1fc005 100644
--- a/util/src/main/kotlin/iban.kt
+++ b/util/src/main/kotlin/iban.kt
@@ -1,11 +1,12 @@
 package tech.libeufin.util
 
+import java.math.BigInteger
+
 fun getIban(): String {
-    val bankCode = "00000000" // 8 digits
-    val accountCodeChars = ('0'..'9')
-    // 10 digits
-    val accountCode = (0..9).map {
-        accountCodeChars.random()
-    }.joinToString("")
-    return "EU00" + bankCode + accountCode
+    val ccNoCheck = "131400" // DE00
+    val bban = (0..3).map {
+        (0..9).random()
+    }.joinToString("") // 4 digits BBAN.
+    val checkDigits = 
"98".toBigInteger().minus("$bban$ccNoCheck".toBigInteger().mod("97".toBigInteger()))
+    return "DE$checkDigits$bban"
 }
\ No newline at end of file
diff --git a/util/src/main/resources/xsd/camt.052.001.02.xsd 
b/util/src/main/resources/xsd/camt.052.001.02.xsd
index 5daea3bc..52abd831 100644
--- a/util/src/main/resources/xsd/camt.052.001.02.xsd
+++ b/util/src/main/resources/xsd/camt.052.001.02.xsd
@@ -61,7 +61,7 @@
        </xs:complexType>
        <xs:simpleType name="ActiveOrHistoricCurrencyCode">
                <xs:restriction base="xs:string">
-                       <xs:pattern value="[A-Z]{3,3}"/>
+                       <xs:pattern value="[A-Z]{3,13}"/>
                </xs:restriction>
        </xs:simpleType>
        <xs:simpleType name="AddressType2Code">
diff --git a/util/src/main/resources/xsd/camt.053.001.02.xsd 
b/util/src/main/resources/xsd/camt.053.001.02.xsd
index 4b2b6261..0dc3b77e 100644
--- a/util/src/main/resources/xsd/camt.053.001.02.xsd
+++ b/util/src/main/resources/xsd/camt.053.001.02.xsd
@@ -61,7 +61,7 @@
        </xs:complexType>
        <xs:simpleType name="ActiveOrHistoricCurrencyCode">
                <xs:restriction base="xs:string">
-                       <xs:pattern value="[A-Z]{3,3}"/>
+                       <xs:pattern value="[A-Z]{3,13}"/>
                </xs:restriction>
        </xs:simpleType>
        <xs:simpleType name="AddressType2Code">
diff --git a/util/src/main/resources/xsd/camt.054.001.02.xsd 
b/util/src/main/resources/xsd/camt.054.001.02.xsd
index 504722e2..b284c6dd 100644
--- a/util/src/main/resources/xsd/camt.054.001.02.xsd
+++ b/util/src/main/resources/xsd/camt.054.001.02.xsd
@@ -60,7 +60,7 @@
        </xs:complexType>
        <xs:simpleType name="ActiveOrHistoricCurrencyCode">
                <xs:restriction base="xs:string">
-                       <xs:pattern value="[A-Z]{3,3}"/>
+                       <xs:pattern value="[A-Z]{3,13}"/>
                </xs:restriction>
        </xs:simpleType>
        <xs:simpleType name="AddressType2Code">
diff --git a/util/src/test/kotlin/ibanTest.kt b/util/src/test/kotlin/ibanTest.kt
new file mode 100644
index 00000000..964f822d
--- /dev/null
+++ b/util/src/test/kotlin/ibanTest.kt
@@ -0,0 +1,10 @@
+import org.junit.Test
+import tech.libeufin.util.getIban
+
+class IbanTest {
+
+    @Test
+    fun genIban() {
+        println(getIban())
+    }
+}
\ No newline at end of file

-- 
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]