[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: Installing kotlinx.serialization.,
gnunet <=