[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated (9187644 -> 16e22ca)
From: |
gnunet |
Subject: |
[libeufin] branch master updated (9187644 -> 16e22ca) |
Date: |
Wed, 08 Apr 2020 16:54:04 +0200 |
This is an automated email from the git hooks/post-receive script.
marcello pushed a change to branch master
in repository libeufin.
from 9187644 define authenticator helper
new 523a32f Helper to extract URI parameters.
new 16e22ca Completing the incoming history monitor.
The 2 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:
nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 16 +-
.../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 21 +++
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 19 ++-
nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 163 ++++++++++++++++++---
.../src/main/kotlin/tech/libeufin/sandbox/DB.kt | 8 -
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 5 -
6 files changed, 190 insertions(+), 42 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 372d414..46d3b8f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,5 +1,6 @@
package tech.libeufin.nexus
+import io.ktor.application.ApplicationCall
import io.ktor.http.HttpStatusCode
/**
@@ -36,6 +37,26 @@ fun expectIdTransaction(param: String?):
EbicsSubscriberEntity {
return EbicsSubscriberEntity.findById(param) ?: throw
NexusError(HttpStatusCode.NotFound, "Subscriber: $param not found")
}
+fun ApplicationCall.expectUrlParameter(name: String): String {
+ return this.request.queryParameters[name]
+ ?: 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 9c8ef51..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
@@ -101,7 +107,6 @@ class Taler(app: Route) {
private data class TalerOutgoingHistory(
var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> =
mutableListOf()
)
-
/**
* Test APIs' data structures.
*/
@@ -116,21 +121,130 @@ class Taler(app: Route) {
val row_id: Long
)
- // throws error if password is wrong
- private fun authenticateRequest(authorization: String?) {
+ private fun SizedIterable<TalerIncomingPaymentEntry>.orderTaler(start:
Long): List<TalerIncomingPaymentEntry> {
+ return if (start < 0) {
+ this.sortedByDescending { it.id }
+ } else {
+ this.sortedBy { it.id }
+ }
+ }
+
+ /**
+ * Test HTTP basic auth. Throws error if password is wrong
+ *
+ * @param authorization the Authorization:-header line.
+ * @return subscriber id
+ */
+ 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
+ }
+ }
}
- fun testAuth(app: Route) {
+ /**
+ * 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
+ }
+ }
+
+ /**
+ * 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)
@@ -138,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
@@ -181,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
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index ea85bf5..2af9c6a 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -225,7 +225,6 @@ fun main() {
}
}
routing {
-
post("/{id}/history") {
val req = call.receive<CustomerHistoryRequest>()
val customer = findCustomer(call.parameters["id"])
@@ -252,7 +251,6 @@ fun main() {
call.respond(ret)
return@post
}
-
get("/{id}/balance") {
val customer = findCustomer(call.parameters["id"])
val balance = calculateBalance(customer.id.value, null, null)
@@ -264,7 +262,6 @@ fun main() {
)
return@get
}
-
get("/admin/subscribers") {
var ret = AdminGetSubscribers()
transaction {
@@ -277,7 +274,6 @@ fun main() {
call.respond(ret)
return@get
}
-
post("/admin/add/subscriber") {
val body = call.receive<AdminAddSubscriberRequest>()
@@ -332,7 +328,6 @@ fun main() {
HttpStatusCode.OK
)
return@post
-
}
get("/ebics/hosts/{id}") {
val resp = transaction {
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [libeufin] branch master updated (9187644 -> 16e22ca),
gnunet <=