gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 02/02: Completing the incoming history monitor.


From: gnunet
Subject: [libeufin] 02/02: Completing the incoming history monitor.
Date: Wed, 08 Apr 2020 16:54:06 +0200

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

marcello pushed a commit to branch master
in repository libeufin.

commit 16e22caf9a4117beb7006ae997981f5fe6dc9495
Author: Marcello Stanisci <address@hidden>
AuthorDate: Wed Apr 8 16:53:36 2020 +0200

    Completing the incoming history monitor.
---
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  16 ++-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  15 ++
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  19 ++-
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 159 ++++++++++++++++++---
 .../src/main/kotlin/tech/libeufin/sandbox/DB.kt    |   8 --
 5 files changed, 181 insertions(+), 36 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index 61922cc..c0f8ec9 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -1,12 +1,10 @@
 package tech.libeufin.nexus
 
+import io.ktor.http.HttpStatusCode
 import org.jetbrains.exposed.dao.*
 import org.jetbrains.exposed.sql.*
 import org.jetbrains.exposed.sql.transactions.TransactionManager
 import org.jetbrains.exposed.sql.transactions.transaction
-import org.joda.time.DateTime
-import tech.libeufin.nexus.EbicsSubscribersTable.entityId
-import tech.libeufin.nexus.EbicsSubscribersTable.primaryKey
 import tech.libeufin.util.IntIdTableWithAmount
 import java.sql.Connection
 
@@ -20,7 +18,17 @@ object TalerIncomingPayments: LongIdTable() {
 }
 
 class TalerIncomingPaymentEntry(id: EntityID<Long>) : LongEntity(id) {
-    companion object : 
LongEntityClass<TalerIncomingPaymentEntry>(TalerIncomingPayments)
+    companion object : 
LongEntityClass<TalerIncomingPaymentEntry>(TalerIncomingPayments) {
+        override fun new(init: TalerIncomingPaymentEntry.() -> Unit): 
TalerIncomingPaymentEntry {
+            val newRow = super.new(init)
+            if (newRow.id.value == Long.MAX_VALUE) {
+                throw NexusError(
+                    HttpStatusCode.InsufficientStorage, "Cannot store rows 
anymore"
+                )
+            }
+            return newRow
+        }
+    }
     var payment by EbicsRawBankTransactionEntry referencedOn 
TalerIncomingPayments.payment
     var valid by TalerIncomingPayments.valid
     var processed by TalerIncomingPayments.processed
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 635f35a..46d3b8f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -42,6 +42,21 @@ fun ApplicationCall.expectUrlParameter(name: String): String 
{
         ?: throw NexusError(HttpStatusCode.BadRequest, "Parameter '$name' not 
provided in URI")
 }
 
+fun expectLong(param: String): Long {
+    return try {
+        param.toLong()
+    } catch (e: Exception) {
+        throw NexusError(HttpStatusCode.BadRequest,"'$param' is not Long")
+    }
+}
+
+fun expectLong(param: String?): Long? {
+    if (param != null) {
+        return expectLong(param)
+    }
+    return null
+}
+
 /* Needs a transaction{} block to be called */
 fun expectAcctidTransaction(param: String?): EbicsAccountInfoEntity {
     if (param == null) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 7daca4d..ec3fe96 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -40,6 +40,7 @@ import io.ktor.routing.post
 import io.ktor.routing.routing
 import io.ktor.server.engine.embeddedServer
 import io.ktor.server.netty.Netty
+import org.jetbrains.exposed.sql.SizedIterable
 import org.jetbrains.exposed.sql.StdOutSqlLogger
 import org.jetbrains.exposed.sql.addLogger
 import org.jetbrains.exposed.sql.and
@@ -138,6 +139,23 @@ fun getSubscriberDetailsFromBankAccount(bankAccountId: 
String): EbicsClientSubsc
     }
 }
 
+/**
+ * Given a subscriber id, returns the _list_ of bank accounts associated to it.
+ * @param id the subscriber id
+ * @return the query set containing the subscriber's bank accounts
+ */
+fun getBankAccountsInfoFromId(id: String): 
SizedIterable<EbicsAccountInfoEntity> {
+    val list = transaction {
+        EbicsAccountInfoEntity.find {
+            EbicsAccountsInfoTable.subscriber eq id
+        }
+    }
+    if (list.empty()) throw NexusError(
+        HttpStatusCode.NotFound, "This subscriber '$id' did never fetch its 
own bank accounts, request HTD first."
+    )
+    return list
+}
+
 fun getSubscriberDetailsFromId(id: String): EbicsClientSubscriberDetails {
     return transaction {
         val subscriber = EbicsSubscriberEntity.findById(id) ?: throw 
NexusError(
@@ -176,7 +194,6 @@ fun getSubscriberDetailsFromId(id: String): 
EbicsClientSubscriberDetails {
  * Needs to be called within a transaction block.
  */
 fun createPain001document(pain001Entity: Pain001Entity): String {
-
     /**
      * Every PAIN.001 document contains at least three IDs:
      *
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 4cf24f8..268cb8e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -3,12 +3,18 @@ package tech.libeufin.nexus
 import io.ktor.application.call
 import io.ktor.http.ContentType
 import io.ktor.http.HttpStatusCode
+import io.ktor.response.respond
 import io.ktor.response.respondText
 import io.ktor.routing.Route
 import io.ktor.routing.get
 import io.ktor.routing.post
-import org.jetbrains.exposed.sql.and
+import org.jetbrains.exposed.dao.EntityID
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
 import org.jetbrains.exposed.sql.transactions.transaction
+import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormat
+import org.joda.time.format.DateTimeFormatter
 import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.base64ToBytes
 import java.lang.Exception
@@ -115,24 +121,130 @@ class Taler(app: Route) {
         val row_id: Long
     )
 
+    private fun SizedIterable<TalerIncomingPaymentEntry>.orderTaler(start: 
Long): List<TalerIncomingPaymentEntry> {
+        return if (start < 0) {
+            this.sortedByDescending { it.id }
+        } else {
+            this.sortedBy { it.id }
+        }
+    }
+
     /**
-     * throws error if password is wrong
+     * Test HTTP basic auth.  Throws error if password is wrong
+     *
      * @param authorization the Authorization:-header line.
+     * @return subscriber id
      */
-    private fun authenticateRequest(authorization: String?) {
+    private fun authenticateRequest(authorization: String?): String {
         val headerLine = authorization ?: throw NexusError(
             HttpStatusCode.BadRequest, "Authentication:-header line not found"
         )
         logger.debug("Checking for authorization: $headerLine")
-        transaction {
+        val subscriber = transaction {
             val (user, pass) = extractUserAndHashedPassword(headerLine)
             EbicsSubscriberEntity.find {
                 EbicsSubscribersTable.id eq user and 
(EbicsSubscribersTable.password eq SerialBlob(pass))
             }.firstOrNull()
         } ?: throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
+        return subscriber.id.value
+    }
+
+    /**
+     * Implement the Taler wire API transfer method.
+     */
+    private fun transfer(app: Route) {
+
+    }
+
+    private fun getPaytoUri(name: String, iban: String, bic: String): String {
+        return "payto://$iban/$bic?receiver-name=$name"
+    }
+
+    /**
+     * Builds the comparison operator for history entries based on the
+     * sign of 'delta'
+     */
+    private fun getComparisonOperator(delta: Long, start: Long): Op<Boolean> {
+        return if (delta < 0) {
+            Expression.build {
+                TalerIncomingPayments.id less start
+            }
+        } else {
+            Expression.build {
+                TalerIncomingPayments.id greater start
+            }
+        }
+    }
+
+    /**
+     * Helper handling 'start' being optional and its dependence on 'delta'.
+     */
+    private fun handleStartArgument(start: String?, delta: Long): Long {
+        return expectLong(start) ?: if (delta >= 0) {
+            /**
+             * Using -1 as the smallest value, as some DBMS might use 0 and 
some
+             * others might use 1 as the smallest row id.
+             */
+            -1
+        } else {
+            /**
+             * NOTE: the database currently enforces there MAX_VALUE is always
+             * strictly greater than any row's id in the database.  In fact, 
the
+             * database throws exception whenever a new row is going to occupy
+             * the MAX_VALUE with its id.
+             */
+            Long.MAX_VALUE
+        }
     }
 
-    fun testAuth(app: Route) {
+    /**
+     * Respond with ONLY the good transfer made to the exchange.
+     * A 'good' transfer is one whose subject line is a plausible
+     * EdDSA public key encoded in Crockford base32.
+     */
+    private fun historyIncoming(app: Route) {
+        app.get("/taler/history/incoming") {
+            val subscriberId = 
authenticateRequest(call.request.headers["Authorization"])
+            val delta: Long = expectLong(call.expectUrlParameter("delta"))
+            val start: Long = 
handleStartArgument(call.request.queryParameters["start"], delta)
+            val history = TalerIncomingHistory()
+            val cmpOp = getComparisonOperator(delta, start)
+            transaction {
+                val subscriberBankAccount = 
getBankAccountsInfoFromId(subscriberId)
+                TalerIncomingPaymentEntry.find {
+                    TalerIncomingPayments.valid eq true and cmpOp
+                }.orderTaler(start).forEach {
+                    history.incoming_transactions.add(
+                        TalerIncomingBankTransaction(
+                            date = DateTime.parse(it.payment.bookingDate, 
DateTimeFormat.forPattern("YYYY-MM-DD")).millis,
+                            row_id = it.id.value,
+                            amount = 
"${it.payment.currency}:${it.payment.amount}",
+                            reserve_pub = 
it.payment.unstructuredRemittanceInformation,
+                            debit_account = getPaytoUri(
+                                it.payment.debitorName, 
it.payment.debitorIban, it.payment.counterpartBic
+                            ),
+                            credit_account = getPaytoUri(
+                                it.payment.creditorName, 
it.payment.creditorIban, subscriberBankAccount.first().bankCode
+                            )
+                        )
+                    )
+                }
+            }
+            call.respond(history)
+            return@get
+        }
+    }
+
+    /**
+     * Respond with all the transfers that the exchange made to merchants.
+     * It can include also those transfers made to reimburse some invalid
+     * incoming payment.
+     */
+    private fun historyOutgoing(app: Route) {
+
+    }
+
+    private fun testAuth(app: Route) {
         app.get("/taler/test-auth") {
             authenticateRequest(call.request.headers["Authorization"])
             call.respondText("Authenticated!", ContentType.Text.Plain, 
HttpStatusCode.OK)
@@ -140,27 +252,29 @@ class Taler(app: Route) {
         }
     }
 
-    fun digest(app: Route) {
+    private fun digest(app: Route) {
         app.post("/ebics/taler/{id}/digest-incoming-transactions") {
             val id = expectId(call.parameters["id"])
             // first find highest ID value of already processed rows.
             transaction {
-                // avoid re-processing raw payments
-                val latest = 
TalerIncomingPaymentEntry.all().sortedByDescending {
+                /**
+                 * The following query avoids to put a "taler processed" 
flag-column into
+                 * the raw ebics transactions table.  Such table should not 
contain taler-related
+                 * information.
+                 *
+                 * This latestId value points at the latest id in the _raw 
transactions table_
+                 * that was last processed.  On the other hand, the "row_id" 
value that the exchange
+                 * will get along each history element will be the id in the 
_digested entries table_.
+                 */
+                val latestId: Long = 
TalerIncomingPaymentEntry.all().sortedByDescending {
                     it.payment.id
-                }.firstOrNull()
-
-                val payments = if (latest == null) {
-                    EbicsRawBankTransactionEntry.find {
-                        EbicsRawBankTransactionsTable.nexusSubscriber eq id
-                    }
-                } else {
-                    EbicsRawBankTransactionEntry.find {
-                        EbicsRawBankTransactionsTable.id.greater(latest.id) and
-                                (EbicsRawBankTransactionsTable.nexusSubscriber 
eq id)
-                    }
-                }
-                payments.forEach {
+                }.firstOrNull()?.payment?.id?.value ?: -1
+                val subscriberAccount = getBankAccountsInfoFromId(id).first()
+                /* search for fresh transactions having the exchange IBAN in 
the creditor field.  */
+                EbicsRawBankTransactionEntry.find {
+                    EbicsRawBankTransactionsTable.creditorIban eq 
subscriberAccount.iban and
+                            
(EbicsRawBankTransactionsTable.id.greater(latestId))
+                }.forEach {
                     if 
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
                         TalerIncomingPaymentEntry.new {
                             payment = it
@@ -183,8 +297,7 @@ class Taler(app: Route) {
         }
     }
 
-    fun refund(app: Route) {
-
+    private fun refund(app: Route) {
         
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
             transaction {
                 val subscriber = expectIdTransaction(call.parameters["id"])
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 1916df6..b9d872b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -221,10 +221,8 @@ object EbicsDownloadTransactionsTable : IdTable<String>() {
     val receiptReceived = bool("receiptReceived")
 }
 
-
 class EbicsDownloadTransactionEntity(id: EntityID<String>) : 
Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsDownloadTransactionEntity>(EbicsDownloadTransactionsTable)
-
     var orderType by EbicsDownloadTransactionsTable.orderType
     var host by EbicsHostEntity referencedOn 
EbicsDownloadTransactionsTable.host
     var subscriber by EbicsSubscriberEntity referencedOn 
EbicsDownloadTransactionsTable.subscriber
@@ -235,7 +233,6 @@ class EbicsDownloadTransactionEntity(id: EntityID<String>) 
: Entity<String>(id)
     var receiptReceived by EbicsDownloadTransactionsTable.receiptReceived
 }
 
-
 object EbicsUploadTransactionsTable : IdTable<String>() {
     override val id = text("transactionID").entityId()
     val orderType = text("orderType")
@@ -247,7 +244,6 @@ object EbicsUploadTransactionsTable : IdTable<String>() {
     val transactionKeyEnc = blob("transactionKeyEnc")
 }
 
-
 class EbicsUploadTransactionEntity(id: EntityID<String>) : Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsUploadTransactionEntity>(EbicsUploadTransactionsTable)
 
@@ -260,7 +256,6 @@ class EbicsUploadTransactionEntity(id: EntityID<String>) : 
Entity<String>(id) {
     var transactionKeyEnc by EbicsUploadTransactionsTable.transactionKeyEnc
 }
 
-
 object EbicsOrderSignaturesTable : IntIdTable() {
     val orderID = text("orderID")
     val orderType = text("orderType")
@@ -270,10 +265,8 @@ object EbicsOrderSignaturesTable : IntIdTable() {
     val signatureValue = blob("signatureValue")
 }
 
-
 class EbicsOrderSignatureEntity(id: EntityID<Int>) : IntEntity(id) {
     companion object : 
IntEntityClass<EbicsOrderSignatureEntity>(EbicsOrderSignaturesTable)
-
     var orderID by EbicsOrderSignaturesTable.orderID
     var orderType by EbicsOrderSignaturesTable.orderType
     var partnerID by EbicsOrderSignaturesTable.partnerID
@@ -291,7 +284,6 @@ object EbicsUploadTransactionChunksTable : 
IdTable<String>() {
 
 class EbicsUploadTransactionChunkEntity(id : EntityID<String>): 
Entity<String>(id) {
     companion object : EntityClass<String, 
EbicsUploadTransactionChunkEntity>(EbicsUploadTransactionChunksTable)
-
     var chunkIndex by EbicsUploadTransactionChunksTable.chunkIndex
     var chunkContent by EbicsUploadTransactionChunksTable.chunkContent
 }

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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