gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Installing kotlinx.serialization.


From: gnunet
Subject: [libeufin] branch master updated: Installing kotlinx.serialization.
Date: Tue, 12 Sep 2023 16:04:27 +0200

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

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 0856649d Installing kotlinx.serialization.
0856649d is described below

commit 0856649d9d96c97f466533a86210101b5c81d235
Author: MS <ms@taler.net>
AuthorDate: Tue Sep 12 16:01:54 2023 +0200

    Installing kotlinx.serialization.
    
    Testing user registration without the only-admin policy.
---
 bank/build.gradle                               |  7 +-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 87 +++++++++++++++++++------
 bank/src/test/kotlin/JsonTest.kt                | 26 ++++++++
 bank/src/test/kotlin/LibeuFinApiTest.kt         | 28 +++++++-
 4 files changed, 121 insertions(+), 27 deletions(-)

diff --git a/bank/build.gradle b/bank/build.gradle
index a7925da7..46e7aba8 100644
--- a/bank/build.gradle
+++ b/bank/build.gradle
@@ -4,6 +4,7 @@ plugins {
     id 'application'
     id 'org.jetbrains.kotlin.jvm'
     id "com.github.johnrengelman.shadow" version "5.2.0"
+    id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.22'
 }
 
 sourceCompatibility = "11"
@@ -37,6 +38,7 @@ task installToPrefix(type: Copy) {
     into "${project.findProperty('prefix') ?: '/tmp'}"
 }
 apply plugin: 'kotlin-kapt'
+// apply plugin: 'kotlinx-serialization'
 
 sourceSets {
     main.java.srcDirs = ['src/main/kotlin']
@@ -72,9 +74,8 @@ dependencies {
     implementation "io.ktor:ktor-server-netty:$ktor_version"
     implementation "io.ktor:ktor-server-test-host:$ktor_version"
     implementation "io.ktor:ktor-auth:$ktor_auth_version"
-    implementation "io.ktor:ktor-serialization-jackson:$ktor_version"
-    // implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
-    // implementation("io.ktor:ktor-serialization-gson:$ktor_version")
+    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
+    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
     implementation "io.ktor:ktor-server-request-validation:$ktor_version"
 
     testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.5.21'
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 8b228fa8..2662a051 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -1,41 +1,53 @@
 package tech.libeufin.bank
 
 import io.ktor.http.*
-import io.ktor.serialization.jackson.*
 import io.ktor.server.application.*
 import io.ktor.server.plugins.*
 import io.ktor.server.plugins.requestvalidation.*
-import io.ktor.server.plugins.callloging.*
 import io.ktor.server.plugins.contentnegotiation.*
+import io.ktor.serialization.kotlinx.json.*
+import io.ktor.server.plugins.callloging.*
+import kotlinx.serialization.*
 import io.ktor.server.plugins.cors.routing.*
 import io.ktor.server.plugins.statuspages.*
 import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
+import kotlinx.serialization.json.Json
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
-import javax.xml.bind.ValidationException
 
 // GLOBALS
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank")
 val db = Database(System.getProperty("BANK_DB_CONNECTION_STRING"))
+const val GENERIC_JSON_INVALID = 22
+const val GENERIC_PARAMETER_MALFORMED = 26
+const val GENERIC_PARAMETER_MISSING = 25
 
 // TYPES
+@Serializable
+data class TalerError(
+    val code: Int,
+    val hint: String? = null
+)
+
+@Serializable
 data class ChallengeContactData(
     val email: String? = null,
     val phone: String? = null
 )
+@Serializable
 data class RegisterAccountRequest(
     val username: String,
     val password: String,
     val name: String,
     val is_public: Boolean = false,
     val is_taler_exchange: Boolean = false,
-    val challenge_contact_data: ChallengeContactData,
-    val cashout_payto_uri: String?,
-    val internal_payto_uri: String?
+    val challenge_contact_data: ChallengeContactData? = null,
+    val cashout_payto_uri: String? = null,
+    val internal_payto_uri: String? = null
 )
 
 // Generates a new Payto-URI with IBAN scheme.
@@ -45,8 +57,11 @@ fun parseTalerAmount(amount: String): TalerAmount {
     val match = Regex(amountWithCurrencyRe).find(amount) ?:
     throw badRequest("Invalid amount")
     val value = match.destructured.component2()
-    val fraction = match.destructured.component3().substring(1)
-    return TalerAmount(value.toLong(), fraction.toInt())
+    val fraction: Int = match.destructured.component3().run {
+        if (this.isEmpty()) return@run 0
+        return@run this.substring(1).toInt()
+    }
+    return TalerAmount(value.toLong(), fraction)
 }
 
 /**
@@ -104,7 +119,6 @@ fun ApplicationCall.myAuth(requiredScope: TokenScope): 
Customer? {
     }
 }
 
-
 val webApp: Application.() -> Unit = {
     install(CallLogging) {
         this.level = Level.DEBUG
@@ -123,7 +137,34 @@ val webApp: Application.() -> Unit = {
         allowCredentials = true
     }
     install(IgnoreTrailingSlash)
-    install(ContentNegotiation) { jackson {} }
+    install(ContentNegotiation) {
+        json(Json {
+            ignoreUnknownKeys = true
+            isLenient = false
+        })
+    }
+    install(RequestValidation)
+    install(StatusPages) {
+        exception<BadRequestException> {call, cause ->
+            // Discouraged use, but the only helpful message.
+            var rootCause: Throwable? = cause.cause
+            while (rootCause?.cause != null)
+                rootCause = rootCause.cause
+            logger.error(rootCause?.message)
+            // Telling apart invalid JSON vs missing parameter vs invalid 
parameter.
+            val talerErrorCode = when(cause) {
+                is MissingRequestParameterException -> 
GENERIC_PARAMETER_MISSING // 25
+                is ParameterConversionException -> GENERIC_PARAMETER_MALFORMED 
// 26
+                else -> GENERIC_JSON_INVALID // 22
+            }
+            call.respond(
+                HttpStatusCode.BadRequest,
+                TalerError(
+                    code = talerErrorCode,
+                    hint = rootCause?.message
+                ))
+        }
+    }
     routing {
         post("/accounts") {
             // check if only admin.
@@ -132,7 +173,7 @@ val webApp: Application.() -> Unit = {
                 val customer: Customer? = call.myAuth(TokenScope.readwrite)
                 if (customer == null || customer.login != "admin")
                     // OK to leak the only-admin policy here?
-                    throw forbidden("Only admin allowed, and it failed to 
authenticate.")
+                    throw unauthorized("Only admin allowed, and it failed to 
authenticate.")
             }
             // auth passed, proceed with activity.
             val req = call.receive<RegisterAccountRequest>()
@@ -141,19 +182,23 @@ val webApp: Application.() -> Unit = {
                 throw conflict("Username '${req.username}' is reserved.")
             // Checking imdepotency.
             val maybeCustomerExists = db.customerGetFromLogin(req.username)
-            if (maybeCustomerExists != null) {
-                val bankingInfo = 
db.bankAccountGetFromOwnerId(maybeCustomerExists.expectRowId())
-                    ?: throw internalServerError("Existing customer had no 
bank account!")
+            // Can be null if previous call crashed before completion.
+            val maybeHasBankAccount = maybeCustomerExists.run {
+                if (this == null) return@run null
+                db.bankAccountGetFromOwnerId(this.expectRowId())
+            }
+            if (maybeCustomerExists != null && maybeHasBankAccount != null) {
+                logger.debug("Registering username was found: 
${maybeCustomerExists.login}")
                 // Checking _all_ the details are the same.
                 val isIdentic =
                     maybeCustomerExists.name == req.name &&
-                    maybeCustomerExists.email == 
req.challenge_contact_data.email &&
-                    maybeCustomerExists.phone == 
req.challenge_contact_data.phone &&
+                    maybeCustomerExists.email == 
req.challenge_contact_data?.email &&
+                    maybeCustomerExists.phone == 
req.challenge_contact_data?.phone &&
                     maybeCustomerExists.cashoutPayto == req.cashout_payto_uri 
&&
                     maybeCustomerExists.passwordHash == 
CryptoUtil.hashpw(req.password) &&
-                    bankingInfo.isPublic == req.is_public &&
-                    bankingInfo.isTalerExchange == req.is_taler_exchange &&
-                    bankingInfo.internalPaytoUri == req.internal_payto_uri
+                    maybeHasBankAccount.isPublic == req.is_public &&
+                    maybeHasBankAccount.isTalerExchange == 
req.is_taler_exchange &&
+                    maybeHasBankAccount.internalPaytoUri == 
req.internal_payto_uri
                 if (isIdentic) call.respond(HttpStatusCode.Created)
                 call.respond(HttpStatusCode.Conflict)
             }
@@ -161,8 +206,8 @@ val webApp: Application.() -> Unit = {
             val newCustomer = Customer(
                 login = req.username,
                 name = req.name,
-                email = req.challenge_contact_data.email,
-                phone = req.challenge_contact_data.phone,
+                email = req.challenge_contact_data?.email,
+                phone = req.challenge_contact_data?.phone,
                 cashoutPayto = req.cashout_payto_uri,
                 // Following could be gone, if included in cashout_payto
                 cashoutCurrency = db.configGet("cashout_currency"),
diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt
new file mode 100644
index 00000000..b4599cea
--- /dev/null
+++ b/bank/src/test/kotlin/JsonTest.kt
@@ -0,0 +1,26 @@
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.junit.Test
+
+@Serializable
+data class MyJsonType(
+    val content: String,
+    val n: Int
+)
+
+// Running (de)serialization, only checking that no exceptions are raised.
+class JsonTest {
+    @Test
+    fun serializationTest() {
+        Json.encodeToString(MyJsonType("Lorem Ipsum", 3))
+    }
+    @Test
+    fun deserializationTest() {
+        val serialized = """
+            {"content": "Lorem Ipsum", "n": 3}
+        """.trimIndent()
+        Json.decodeFromString<MyJsonType>(serialized)
+    }
+
+}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index c292d796..501a447b 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -2,11 +2,27 @@ import io.ktor.client.plugins.*
 import io.ktor.client.request.*
 import io.ktor.http.*
 import io.ktor.server.testing.*
+import kotlinx.serialization.json.Json
 import org.junit.Test
 import tech.libeufin.bank.Database
+import tech.libeufin.bank.RegisterAccountRequest
 import tech.libeufin.bank.webApp
+import tech.libeufin.util.execCommand
 
 class LibeuFinApiTest {
+    fun initDb(): Database {
+        execCommand(
+            listOf(
+                "libeufin-bank-dbinit",
+                "-d",
+                "libeufincheck",
+                "-r"
+            ),
+            throwIfFails = true
+        )
+        val db = Database("jdbc:postgresql:///libeufincheck")
+        return db
+    }
     @Test
     fun createAccountTest() {
         testApplication {
@@ -14,12 +30,18 @@ class LibeuFinApiTest {
                 "BANK_DB_CONNECTION_STRING",
                 "jdbc:postgresql:///libeufincheck"
             )
-            val db = Database("jdbc:postgresql:///libeufincheck")
+            val db = initDb()
             db.configSet("max_debt_ordinary_customers", "KUDOS:11")
+            db.configSet("only_admin_registrations", "yes")
             application(webApp)
-            client.post("/test-json") {
-                expectSuccess = true
+            client.post("/accounts") {
                 contentType(ContentType.Application.Json)
+                basicAuth("admin", "bar")
+                setBody("""{
+                    "username": "foo",
+                    "password": "bar",
+                    "name": "Jane"
+                }""".trimIndent())
             }
         }
     }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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