[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 08/08: Fixes after wallet tests harness.
From: |
gnunet |
Subject: |
[libeufin] 08/08: Fixes after wallet tests harness. |
Date: |
Wed, 10 Nov 2021 16:22:51 +0100 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
commit fe22997387eefa9fb205384fe59f42d2480ea425
Author: ms <ms@taler.net>
AuthorDate: Wed Nov 10 16:21:04 2021 +0100
Fixes after wallet tests harness.
- implement TWG /admin/add-incoming.
- generate checksum-valid IBANs.
---
access-api-stash/AccessApiNexus.kt | 210 +++++++++++++++++++++
.../main/kotlin/tech/libeufin/sandbox/Helpers.kt | 10 +-
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 50 ++++-
util/src/main/kotlin/JSON.kt | 6 +
util/src/main/kotlin/Payto.kt | 4 +-
util/src/main/kotlin/iban.kt | 15 +-
util/src/test/kotlin/ibanTest.kt | 10 +
7 files changed, 288 insertions(+), 17 deletions(-)
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/Helpers.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index 81974703..2e927000 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -176,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,
@@ -195,7 +195,7 @@ fun wireTransfer(
"Demobank '$demobank' not found"
)
- Triple(debitAccount, creditAccount, demoBank)
+ Triple(debitAccountDb, creditAccountDb, demoBank)
}
/**
@@ -228,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 {
@@ -281,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 {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 454db034..2e405b40 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -986,6 +986,39 @@ 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") {
@@ -1283,8 +1316,8 @@ val sandboxApp: Application.() -> Unit = {
}
// Create new customer.
requireValidResourceName(req.username)
- transaction {
- BankAccountEntity.new {
+ val bankAccount = transaction {
+ val bankAccount = BankAccountEntity.new {
iban = getIban()
/**
* For now, keep same semantics of Pybank: a
username
@@ -1299,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/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/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.
- [libeufin] branch master updated (a6661dff -> fe229973), gnunet, 2021/11/10
- [libeufin] 02/08: fix visibility issue, gnunet, 2021/11/10
- [libeufin] 01/08: Finish basic auth implementation., gnunet, 2021/11/10
- [libeufin] 03/08: adapt after wallet tests harness, gnunet, 2021/11/10
- [libeufin] 06/08: allow longer currency names, gnunet, 2021/11/10
- [libeufin] 07/08: fixes after wallet harness, gnunet, 2021/11/10
- [libeufin] 04/08: Fixes after wallet tests harness., gnunet, 2021/11/10
- [libeufin] 05/08: fix exchange suggestion, gnunet, 2021/11/10
- [libeufin] 08/08: Fixes after wallet tests harness.,
gnunet <=