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