gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (7e01743 -> 69f7cd2)


From: gnunet
Subject: [libeufin] branch master updated (7e01743 -> 69f7cd2)
Date: Mon, 20 Apr 2020 21:24:42 +0200

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

marcello pushed a change to branch master
in repository libeufin.

    from 7e01743  fix build
     new 3eb59a3  fix test
     new 69f7cd2  Get bank testcases up to 'credit-1' to pass.

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    |   2 +-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |  11 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |  82 ++++++++++----
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 120 +++++++++++++++------
 nexus/src/test/kotlin/authentication.kt            |  14 +--
 5 files changed, 161 insertions(+), 68 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index a7b57f4..af94e1d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -96,7 +96,7 @@ object EbicsRawBankTransactionsTable : LongIdTable() {
     val debitorIban = text("debitorIban")
     val debitorName = text("debitorName")
     val counterpartBic = text("counterpartBic")
-    val bookingDate = text("bookingDate")
+    val bookingDate = long("bookingDate")
     val status = text("status") // BOOK, ..
 }
 
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index e5b38bf..3e04eed 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -4,6 +4,8 @@ import io.ktor.application.ApplicationCall
 import io.ktor.http.HttpStatusCode
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
+import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormat
 import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.base64ToBytes
 import javax.sql.rowset.serial.SerialBlob
@@ -103,10 +105,9 @@ fun extractUserAndHashedPassword(authorizationHeader: 
String): Pair<String, Byte
  * @return subscriber id
  */
 fun authenticateRequest(authorization: String?): String {
-    val headerLine = authorization ?: throw NexusError(
+    val headerLine = if (authorization == null) throw NexusError(
         HttpStatusCode.BadRequest, "Authentication:-header line not found"
-    )
-    logger.debug("Checking for authorization: $headerLine")
+    ) else authorization
     val subscriber = transaction {
         val (user, pass) = extractUserAndHashedPassword(headerLine)
         EbicsSubscriberEntity.find {
@@ -115,3 +116,7 @@ fun authenticateRequest(authorization: String?): String {
     } ?: throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
     return subscriber.id.value
 }
+
+fun parseDate(date: String): DateTime {
+    return DateTime.parse(date, DateTimeFormat.forPattern("YYYY-MM-DD"))
+}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 3f86387..e751c0d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -22,15 +22,13 @@ package tech.libeufin.nexus
 import io.ktor.application.ApplicationCallPipeline
 import io.ktor.application.call
 import io.ktor.application.install
-import io.ktor.auth.Authentication
-import io.ktor.auth.basic
 import io.ktor.client.HttpClient
-import io.ktor.features.CallLogging
-import io.ktor.features.ContentNegotiation
-import io.ktor.features.StatusPages
+import io.ktor.features.*
 import io.ktor.gson.gson
 import io.ktor.http.ContentType
 import io.ktor.http.HttpStatusCode
+import io.ktor.request.ApplicationReceivePipeline
+import io.ktor.request.ApplicationReceiveRequest
 import io.ktor.request.receive
 import io.ktor.request.uri
 import io.ktor.response.respond
@@ -40,6 +38,11 @@ import io.ktor.routing.post
 import io.ktor.routing.routing
 import io.ktor.server.engine.embeddedServer
 import io.ktor.server.netty.Netty
+import io.ktor.util.KtorExperimentalAPI
+import kotlinx.coroutines.io.ByteReadChannel
+import kotlinx.coroutines.io.jvm.javaio.toByteReadChannel
+import kotlinx.coroutines.io.jvm.javaio.toInputStream
+import kotlinx.io.core.ExperimentalIoApi
 import org.jetbrains.exposed.sql.SizedIterable
 import org.jetbrains.exposed.sql.StdOutSqlLogger
 import org.jetbrains.exposed.sql.addLogger
@@ -60,6 +63,8 @@ import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
 import java.util.*
+import java.util.zip.Inflater
+import java.util.zip.InflaterInputStream
 import javax.crypto.EncryptedPrivateKeyInfo
 import javax.sql.rowset.serial.SerialBlob
 
@@ -86,6 +91,10 @@ data class NexusError(val statusCode: HttpStatusCode, val 
reason: String) : Exce
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
 
+fun isProduction(): Boolean {
+    return System.getenv("NEXUS_PRODUCTION") != null
+}
+
 fun getSubscriberEntityFromId(id: String): EbicsSubscriberEntity {
     return transaction {
         EbicsSubscriberEntity.findById(id) ?: throw NexusError(
@@ -146,14 +155,33 @@ fun getSubscriberDetailsFromBankAccount(bankAccountId: 
String): EbicsClientSubsc
  * is guaranteed to be non empty.
  */
 fun getBankAccountsInfoFromId(id: String): 
SizedIterable<EbicsAccountInfoEntity> {
+    logger.debug("Looking up bank account of user '$id'")
     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."
-    )
+    if (list.empty()) {
+        if (!isProduction()) {
+            /* make up a bank account info object */
+            transaction {
+                EbicsAccountInfoEntity.new("mocked-bank-account") {
+                    subscriber = EbicsSubscriberEntity.findById(id) ?: throw 
NexusError(
+                        HttpStatusCode.NotFound, "Please create subscriber 
'${id}' first."
+                    )
+                    accountHolder = "Tests runner"
+                    iban = "IBAN-FOR-TESTS"
+                    bankCode = "BIC-FOR-TESTS"
+                }
+            }
+            logger.debug("Faked bank account info object for user '$id'")
+        } else throw NexusError(
+            HttpStatusCode.NotFound,
+            "This subscriber '$id' did never fetch its own bank accounts, 
request HTD first."
+        )
+        // call this function again now that the database is augmented with 
the mocked information.
+        return getBankAccountsInfoFromId(id)
+    }
     return list
 }
 
@@ -336,6 +364,8 @@ fun createPain001entity(entry: Pain001Data, 
debtorAccountId: String): Pain001Ent
     }
 }
 
+@ExperimentalIoApi
+@KtorExperimentalAPI
 fun main() {
     dbCreateTables()
     testData()
@@ -343,6 +373,7 @@ fun main() {
         expectSuccess = false // this way, it does not throw exceptions on != 
200 responses.
     }
     val server = embeddedServer(Netty, port = 5001) {
+
         install(CallLogging) {
             this.level = Level.DEBUG
             this.logger = tech.libeufin.nexus.logger
@@ -370,12 +401,13 @@ fun main() {
                     cause.statusCode
                 )
             }
-            exception<javax.xml.bind.UnmarshalException> { cause ->
-                logger.error("Exception while handling '${call.request.uri}'", 
cause)
+            exception<Exception> { cause ->
+                logger.error("Uncaught exception while handling 
'${call.request.uri}'", cause)
+                logger.error(cause.toString())
                 call.respondText(
-                    "Could not convert string into JAXB\n",
+                    "Internal server error",
                     ContentType.Text.Plain,
-                    HttpStatusCode.NotFound
+                    HttpStatusCode.InternalServerError
                 )
             }
         }
@@ -387,6 +419,18 @@ fun main() {
             }
         }
 
+        receivePipeline.intercept(ApplicationReceivePipeline.Before) {
+            if (this.context.request.headers["Content-Encoding"] == "deflate") 
{
+                logger.debug("About to inflate received data")
+                val deflated = this.subject.value as ByteReadChannel
+                val inflated = InflaterInputStream(deflated.toInputStream())
+                proceedWith(ApplicationReceiveRequest(this.subject.typeInfo, 
inflated.toByteReadChannel()))
+                return@intercept
+            }
+            proceed()
+            return@intercept
+        }
+
         routing {
             get("/") {
                 call.respondText("Hello by Nexus!\n")
@@ -677,12 +721,10 @@ fun main() {
                                     currency = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
                                     amount = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']")
                                     status = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
-                                    bookingDate = 
camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")
+                                    bookingDate = 
parseDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
                                     nexusSubscriber = 
getSubscriberEntityFromId(id)
-                                    creditorName =
-                                        
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
-                                    creditorIban =
-                                        
camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
+                                    creditorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
+                                    creditorIban = 
camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']")
                                     debitorName = 
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']")
                                     debitorIban = 
camt53doc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
                                     counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
@@ -1415,8 +1457,12 @@ fun main() {
                 call.respondText("Bank keys stored in database\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
                 return@post
             }
+            post("/test/intercept") {
+                call.respondText(call.receive<String>() + "\n")
+                return@post
+            }
         }
     }
     logger.info("Up and running")
     server.start(wait = true)
-}
+}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index b82e516..25de532 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -1,6 +1,8 @@
 package tech.libeufin.nexus
 
+import com.google.gson.Gson
 import io.ktor.application.call
+import io.ktor.content.TextContent
 import io.ktor.http.ContentType
 import io.ktor.http.HttpStatusCode
 import io.ktor.request.receive
@@ -18,6 +20,7 @@ import tech.libeufin.util.Amount
 import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.toZonedString
 import kotlin.math.abs
+import kotlin.math.min
 
 class Taler(app: Route) {
 
@@ -73,8 +76,11 @@ class Taler(app: Route) {
         val debit_account: String
     )
 
+    private data class GnunetTimestamp(
+        val t_ms: Long
+    )
     private data class TalerAddIncomingResponse(
-        val timestamp: Long,
+        val timestamp: GnunetTimestamp,
         val row_id: Long
     )
 
@@ -94,13 +100,36 @@ class Taler(app: Route) {
     /**
      * Helper functions
      */
-
     fun parsePayto(paytoUri: String): Payto {
-        // payto://iban/BIC?/IBAN?name=<name>
-        val match = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri) ?: 
throw
-                NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
-        val (bic, iban, name) = match.destructured
-        return Payto(name, iban, bic.replace("/", ""))
+        /**
+         * First try to parse a "iban"-type payto URI.  If that fails,
+         * then assume a test is being run under the "x-taler-bank" type.
+         * If that one fails too, throw exception.
+         *
+         * Note: since the Nexus doesn't have the notion of "x-taler-bank",
+         * such URIs must yield a iban-compatible tuple of values.  Therefore,
+         * the plain bank account number maps to a "iban", and the <bank 
hostname>
+         * maps to a "bic".
+         */
+
+
+        /**
+         * payto://iban/BIC?/IBAN?name=<name>
+         * payto://x-taler-bank/<bank hostname>/<plain account number>
+         */
+
+        val ibanMatch = 
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
+        if (ibanMatch != null) {
+            val (bic, iban, name) = ibanMatch.destructured
+            return Payto(name, iban, bic.replace("/", ""))
+        }
+        val xTalerBankMatch = 
Regex("payto://x-taler-bank/localhost/([0-9])?").find(paytoUri)
+        if (xTalerBankMatch != null) {
+            val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
+            return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
+        }
+
+        throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI 
($paytoUri)")
     }
 
     fun parseAmount(amount: String): AmountWithCurrency {
@@ -123,9 +152,6 @@ class Taler(app: Route) {
     private fun getPaytoUri(iban: String, bic: String): String {
         return "payto://iban/$iban/$bic"
     }
-    private fun parseDate(date: String): DateTime {
-        return DateTime.parse(date, DateTimeFormat.forPattern("YYYY-MM-DD"))
-    }
 
     /** Builds the comparison operator for history entries based on the sign 
of 'delta'  */
     private fun getComparisonOperator(delta: Int, start: Long): Op<Boolean> {
@@ -158,6 +184,18 @@ class Taler(app: Route) {
         }
     }
 
+    /**
+     * The Taler layer cannot rely on the ktor-internal 
JSON-converter/responder,
+     * because this one adds a "charset" extra information in the Content-Type 
header
+     * that makes the GNUnet JSON parser unhappy.
+     *
+     * The workaround is to explicitly convert the 'data class'-object into a 
JSON
+     * string (what this function does), and use the simpler respondText 
method.
+     */
+    private fun customConverter(body: Any): String {
+        return Gson().toJson(body)
+    }
+
     /** Attach Taler endpoints to the main Web server */
 
     init {
@@ -201,7 +239,8 @@ class Taler(app: Route) {
                     ),
                     exchangeBankAccount.id.value
                 )
-                val rawEbics = if (System.getenv("NEXUS_PRODUCTION") == null) {
+
+                val rawEbics = if (!isProduction()) {
                     EbicsRawBankTransactionEntity.new {
                         sourceFileName = "test"
                         unstructuredRemittanceInformation = 
transferRequest.wtid
@@ -213,7 +252,7 @@ class Taler(app: Route) {
                         creditorName = creditorObj.name
                         creditorIban = creditorObj.iban
                         counterpartBic = creditorObj.bic
-                        bookingDate = DateTime.now().toString("Y-MM-dd")
+                        bookingDate = DateTime.now().millis
                         nexusSubscriber = exchangeBankAccount.subscriber
                         status = "BOOK"
                     }
@@ -264,20 +303,29 @@ class Taler(app: Route) {
                     debitorIban = debtor.iban
                     debitorName = debtor.name
                     counterpartBic = debtor.bic
-                    bookingDate = DateTime.now().toZonedString()
+                    bookingDate = DateTime.now().millis
                     status = "BOOK"
+                    nexusSubscriber = getSubscriberEntityFromId(exchangeId)
                 }
                 /** This payment is "valid by default" and will be returned
                  * as soon as the exchange will ask for new payments.  */
                 val row = TalerIncomingPaymentEntity.new {
                     payment = rawPayment
+                    valid = true
                 }
                 Pair(rawPayment.bookingDate, row.id.value)
             }
-            call.respond(HttpStatusCode.OK, TalerAddIncomingResponse(
-                timestamp = parseDate(bookingDate).millis / 1000,
-                row_id = opaque_row_id
-            ))
+            call.respond(
+                TextContent(
+                    customConverter(
+                        TalerAddIncomingResponse(
+                            timestamp = GnunetTimestamp(bookingDate/ 1000),
+                            row_id = opaque_row_id
+                        )
+                    ),
+                ContentType.Application.Json
+                )
+            )
             return@post
         }
 
@@ -398,9 +446,8 @@ class Taler(app: Route) {
                             row_id = it.id.value,
                             amount = it.amount,
                             wtid = it.wtid,
-                            date = parseDate(it.rawConfirmed?.bookingDate ?: 
throw NexusError(
-                                HttpStatusCode.InternalServerError, "Null 
value met after check, VERY strange.")
-                            ).millis / 1000,
+                            date = it.rawConfirmed?.bookingDate?.div(1000) ?: 
throw NexusError(
+                                HttpStatusCode.InternalServerError, "Null 
value met after check, VERY strange."),
                             credit_account = it.creditAccount,
                             debit_account = 
getPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
                             exchange_base_url = 
"FIXME-to-request-along-subscriber-registration"
@@ -423,26 +470,29 @@ class Taler(app: Route) {
             val startCmpOp = getComparisonOperator(delta, start)
             transaction {
                 val subscriberBankAccount = 
getBankAccountsInfoFromId(subscriberId)
-                TalerIncomingPaymentEntity.find {
+                val orderedPayments = TalerIncomingPaymentEntity.find {
                     TalerIncomingPayments.valid eq true and startCmpOp
-                }.orderTaler(delta).subList(0, abs(delta)).forEach {
-                    history.incoming_transactions.add(
-                        TalerIncomingBankTransaction(
-                            date = parseDate(it.payment.bookingDate).millis / 
1000, // timestamp in seconds
-                            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
+                }.orderTaler(delta)
+                if (orderedPayments.isNotEmpty()) {
+                    orderedPayments.subList(0, min(abs(delta), 
orderedPayments.size)).forEach {
+                        history.incoming_transactions.add(
+                            TalerIncomingBankTransaction(
+                                date = it.payment.bookingDate / 1000, // 
timestamp in seconds
+                                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)
+            call.respond(TextContent(customConverter(history), 
ContentType.Application.Json))
             return@get
         }
     }
diff --git a/nexus/src/test/kotlin/authentication.kt 
b/nexus/src/test/kotlin/authentication.kt
index 60a88e3..e4dd3da 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -5,18 +5,14 @@ import org.jetbrains.exposed.sql.Database
 import org.jetbrains.exposed.sql.SchemaUtils
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
-import org.junit.Before
 import org.junit.Test
 import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.toByteArray
-import tech.libeufin.util.toHexString
-import java.sql.Blob
 import javax.sql.rowset.serial.SerialBlob
 
 class AuthenticationTest {
 
-    @Before
-    fun connectAndMakeTables() {
+    @Test
+    fun dbInvolvingTest() {
         Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
         transaction {
             SchemaUtils.create(EbicsSubscribersTable)
@@ -32,10 +28,6 @@ class AuthenticationTest {
                 encryptionPrivateKey = 
SerialBlob("encryptionPrivateKey".toByteArray())
             }
         }
-    }
-
-    @Test
-    fun manualMethod() {
         // base64 of "username:password" == "dXNlcm5hbWU6cGFzc3dvcmQ="
         val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")
         val result = transaction {
@@ -47,7 +39,7 @@ class AuthenticationTest {
     }
 
     @Test
-    fun testExtractor() {
+    fun basicAuthHeaderTest() {
         val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")
         
assert(CryptoUtil.hashStringSHA256("password").contentEquals(hashedPass))
     }

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



reply via email to

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