gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: RelativeTime parser.


From: gnunet
Subject: [libeufin] branch master updated: RelativeTime parser.
Date: Thu, 14 Sep 2023 16:08:47 +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 211de5e7 RelativeTime parser.
211de5e7 is described below

commit 211de5e71511e0b3f4fbcadd7aa1c4ebf4403ae2
Author: MS <ms@taler.net>
AuthorDate: Thu Sep 14 16:08:24 2023 +0200

    RelativeTime parser.
---
 .idea/workspace.xml                                |  40 ++++----
 bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt |  82 +++++++++++++++-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    | 108 +++++++++++----------
 bank/src/test/kotlin/JsonTest.kt                   |  25 ++++-
 4 files changed, 181 insertions(+), 74 deletions(-)

diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 9cfe445d..cb134728 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -6,11 +6,9 @@
   <component name="ChangeListManager">
     <list default="true" id="9436eb1e-de48-4f11-8ff7-f359340cb458" 
name="Changes" comment="">
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" 
afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change 
beforePath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Database.kt" 
beforeDir="false" 
afterPath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Database.kt" 
afterDir="false" />
       <change 
beforePath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt" 
beforeDir="false" 
afterPath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt" 
afterDir="false" />
       <change 
beforePath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Main.kt" 
beforeDir="false" 
afterPath="$PROJECT_DIR$/bank/src/main/kotlin/tech/libeufin/bank/Main.kt" 
afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/bank/src/test/kotlin/AmountTest.kt" 
beforeDir="false" afterPath="$PROJECT_DIR$/bank/src/test/kotlin/AmountTest.kt" 
afterDir="false" />
-      <change 
beforePath="$PROJECT_DIR$/bank/src/test/kotlin/LibeuFinApiTest.kt" 
beforeDir="false" 
afterPath="$PROJECT_DIR$/bank/src/test/kotlin/LibeuFinApiTest.kt" 
afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/bank/src/test/kotlin/JsonTest.kt" 
beforeDir="false" afterPath="$PROJECT_DIR$/bank/src/test/kotlin/JsonTest.kt" 
afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -72,8 +70,8 @@
     &quot;settings.editor.splitter.proportion&quot;: &quot;0.31419808&quot;
   }
 }</component>
-  <component name="RunManager" 
selected="Gradle.LibeuFinApiTest.createAccountTest">
-    <configuration name="AmountTest.parseAmountTest" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
+  <component name="RunManager" selected="Gradle.JsonTest.unionTypeTest">
+    <configuration name="AmountTest.parseTalerAmountTest" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
       <ExternalSystemSettings>
         <option name="executionName" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
@@ -86,7 +84,7 @@
           <list>
             <option value=":bank:test" />
             <option value="--tests" />
-            <option value="&quot;AmountTest.parseAmountTest&quot;" />
+            <option value="&quot;AmountTest.parseTalerAmountTest&quot;" />
           </list>
         </option>
         <option name="vmOptions" />
@@ -96,20 +94,20 @@
       <DebugAllEnabled>false</DebugAllEnabled>
       <method v="2" />
     </configuration>
-    <configuration name="AmountTest.parseTalerAmountTest" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
+    <configuration name="CryptoUtilTest.passwordHashing" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
       <ExternalSystemSettings>
         <option name="executionName" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="externalSystemIdString" value="GRADLE" />
-        <option name="scriptParameters" value="--quiet" />
+        <option name="scriptParameters" value="" />
         <option name="taskDescriptions">
           <list />
         </option>
         <option name="taskNames">
           <list>
-            <option value=":bank:test" />
+            <option value=":util:test" />
             <option value="--tests" />
-            <option value="&quot;AmountTest.parseTalerAmountTest&quot;" />
+            <option value="&quot;CryptoUtilTest.passwordHashing&quot;" />
           </list>
         </option>
         <option name="vmOptions" />
@@ -119,20 +117,20 @@
       <DebugAllEnabled>false</DebugAllEnabled>
       <method v="2" />
     </configuration>
-    <configuration name="CryptoUtilTest.passwordHashing" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
+    <configuration name="JsonTest.deserializationTest" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
       <ExternalSystemSettings>
         <option name="executionName" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="externalSystemIdString" value="GRADLE" />
-        <option name="scriptParameters" value="" />
+        <option name="scriptParameters" value="--quiet" />
         <option name="taskDescriptions">
           <list />
         </option>
         <option name="taskNames">
           <list>
-            <option value=":util:test" />
+            <option value=":bank:test" />
             <option value="--tests" />
-            <option value="&quot;CryptoUtilTest.passwordHashing&quot;" />
+            <option value="&quot;JsonTest.deserializationTest&quot;" />
           </list>
         </option>
         <option name="vmOptions" />
@@ -142,12 +140,12 @@
       <DebugAllEnabled>false</DebugAllEnabled>
       <method v="2" />
     </configuration>
-    <configuration name="DatabaseTest.bankAccountTest" 
type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
+    <configuration name="JsonTest.unionTypeTest" type="GradleRunConfiguration" 
factoryName="Gradle" temporary="true">
       <ExternalSystemSettings>
         <option name="executionName" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
         <option name="externalSystemIdString" value="GRADLE" />
-        <option name="scriptParameters" value="" />
+        <option name="scriptParameters" value="--quiet" />
         <option name="taskDescriptions">
           <list />
         </option>
@@ -155,7 +153,7 @@
           <list>
             <option value=":bank:test" />
             <option value="--tests" />
-            <option value="&quot;DatabaseTest.bankAccountTest&quot;" />
+            <option value="&quot;JsonTest.unionTypeTest&quot;" />
           </list>
         </option>
         <option name="vmOptions" />
@@ -189,19 +187,19 @@
       <method v="2" />
     </configuration>
     <list>
+      <item itemvalue="Gradle.JsonTest.unionTypeTest" />
       <item itemvalue="Gradle.CryptoUtilTest.passwordHashing" />
-      <item itemvalue="Gradle.DatabaseTest.bankAccountTest" />
       <item itemvalue="Gradle.AmountTest.parseTalerAmountTest" />
-      <item itemvalue="Gradle.AmountTest.parseAmountTest" />
+      <item itemvalue="Gradle.JsonTest.deserializationTest" />
       <item itemvalue="Gradle.LibeuFinApiTest.createAccountTest" />
     </list>
     <recent_temporary>
       <list>
+        <item itemvalue="Gradle.JsonTest.unionTypeTest" />
+        <item itemvalue="Gradle.JsonTest.deserializationTest" />
         <item itemvalue="Gradle.LibeuFinApiTest.createAccountTest" />
         <item itemvalue="Gradle.CryptoUtilTest.passwordHashing" />
         <item itemvalue="Gradle.AmountTest.parseTalerAmountTest" />
-        <item itemvalue="Gradle.DatabaseTest.bankAccountTest" />
-        <item itemvalue="Gradle.AmountTest.parseAmountTest" />
       </list>
     </recent_temporary>
   </component>
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt
index 9ee21a3c..41684525 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Helpers.kt
@@ -20,11 +20,70 @@
 package tech.libeufin.bank
 
 import io.ktor.http.*
-import tech.libeufin.util.getIban
+import tech.libeufin.util.*
 import java.lang.NumberFormatException
 
-// HELPERS.  FIXME: make unit tests for them.
+// HELPERS.
 
+
+
+/**
+ * Performs the HTTP basic authentication.  Returns the
+ * authenticated customer on success, or null otherwise.
+ */
+fun doBasicAuth(encodedCredentials: String): Customer? {
+    val plainUserAndPass = String(base64ToBytes(encodedCredentials), 
Charsets.UTF_8) // :-separated
+    val userAndPassSplit = plainUserAndPass.split(
+        ":",
+        /**
+         * this parameter allows colons to occur in passwords.
+         * Without this, passwords that have colons would be split
+         * and become meaningless.
+         */
+        limit = 2
+    )
+    if (userAndPassSplit.size != 2)
+        throw LibeufinBankException(
+            httpStatus = HttpStatusCode.BadRequest,
+            talerError = TalerError(
+                code = GENERIC_HTTP_HEADERS_MALFORMED, // 23
+                "Malformed Basic auth credentials found in the Authorization 
header."
+            )
+        )
+    val login = userAndPassSplit[0]
+    val plainPassword = userAndPassSplit[1]
+    val maybeCustomer = db.customerGetFromLogin(login) ?: return null
+    if (!CryptoUtil.checkpw(plainPassword, maybeCustomer.passwordHash)) return 
null
+    return maybeCustomer
+}
+
+/* Performs the bearer-token authentication.  Returns the
+ * authenticated customer on success, null otherwise. */
+fun doTokenAuth(
+    token: String,
+    requiredScope: TokenScope, // readonly or readwrite
+): Customer? {
+    val maybeToken: BearerToken = 
db.bearerTokenGet(token.toByteArray(Charsets.UTF_8)) ?: return null
+    val isExpired: Boolean = maybeToken.expirationTime - getNow().toMicro() < 0
+    if (isExpired || maybeToken.scope != requiredScope) return null // FIXME: 
mention the reason?
+    // Getting the related username.
+    return db.customerGetFromRowId(maybeToken.bankCustomer)
+        ?: throw LibeufinBankException(
+            httpStatus = HttpStatusCode.InternalServerError,
+            talerError = TalerError(
+                code = GENERIC_INTERNAL_INVARIANT_FAILURE,
+                hint = "Customer not found, despite token mentions it.",
+            ))
+}
+
+fun unauthorized(hint: String? = null): LibeufinBankException =
+    LibeufinBankException(
+        httpStatus = HttpStatusCode.Unauthorized,
+        talerError = TalerError(
+            code = BANK_LOGIN_FAILED,
+            hint = hint
+        )
+    )
 fun internalServerError(hint: String): LibeufinBankException =
     LibeufinBankException(
         httpStatus = HttpStatusCode.InternalServerError,
@@ -33,9 +92,28 @@ fun internalServerError(hint: String): LibeufinBankException 
=
             hint = hint
         )
     )
+fun badRequest(
+    hint: String? = null,
+    talerErrorCode: Int = GENERIC_JSON_INVALID
+): LibeufinBankException =
+    LibeufinBankException(
+        httpStatus = HttpStatusCode.InternalServerError,
+        talerError = TalerError(
+            code = talerErrorCode,
+            hint = hint
+        )
+    )
 // Generates a new Payto-URI with IBAN scheme.
 fun genIbanPaytoUri(): String = "payto://iban/SANDBOXX/${getIban()}"
 
+/**
+ * This helper takes the serialized version of a Taler Amount
+ * type and parses it into Libeufin's internal representation.
+ * It returns a TalerAmount type, or throws a LibeufinBankException
+ * it the input is invalid.  Such exception will be then caught by
+ * Ktor, transformed into the appropriate HTTP error type, and finally
+ * responded to the client.
+ */
 fun parseTalerAmount(
     amount: String,
     fracDigits: FracDigits = FracDigits.EIGHT
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index b0153781..7b62015a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -16,6 +16,7 @@
  * License along with LibEuFin; see the file COPYING.  If not, see
  * <http://www.gnu.org/licenses/>
  */
+// Main HTTP handlers and related data definitions.
 
 package tech.libeufin.bank
 
@@ -32,7 +33,11 @@ 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 kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.encoding.decodeStructure
+import kotlinx.serialization.json.*
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
@@ -83,60 +88,56 @@ data class RegisterAccountRequest(
     val internal_payto_uri: String? = null
 )
 
-class LibeufinBankException(
-    val httpStatus: HttpStatusCode,
-    val talerError: TalerError
-) : Exception(talerError.hint)
-
+/**
+ * This is the _internal_ representation of a RelativeTime
+ * JSON type.
+ */
+data class RelativeTime(
+    val d_us: Long
+)
 
 /**
- * Performs the HTTP basic authentication.  Returns the
- * authenticated customer on success, or null otherwise.
+ * This custom (de)serializer interprets the RelativeTime JSON
+ * type.  In particular, it is responsible for converting the
+ * "forever" string into Long.MAX_VALUE.  Any other numeric value
+ * is passed as is.
  */
-fun doBasicAuth(encodedCredentials: String): Customer? {
-    val plainUserAndPass = String(base64ToBytes(encodedCredentials), 
Charsets.UTF_8) // :-separated
-    val userAndPassSplit = plainUserAndPass.split(
-        ":",
-        /**
-         * this parameter allows colons to occur in passwords.
-         * Without this, passwords that have colons would be split
-         * and become meaningless.
-         */
-        limit = 2
-    )
-    if (userAndPassSplit.size != 2)
-        throw LibeufinBankException(
-            httpStatus = HttpStatusCode.BadRequest,
-            talerError = TalerError(
-                code = GENERIC_HTTP_HEADERS_MALFORMED, // 23
-                "Malformed Basic auth credentials found in the Authorization 
header."
-            )
-        )
-    val login = userAndPassSplit[0]
-    val plainPassword = userAndPassSplit[1]
-    val maybeCustomer = db.customerGetFromLogin(login) ?: return null
-    if (!CryptoUtil.checkpw(plainPassword, maybeCustomer.passwordHash)) return 
null
-    return maybeCustomer
-}
+object RelativeTimeSerializer : KSerializer<RelativeTime> {
+    override fun serialize(encoder: Encoder, value: RelativeTime) {
+        throw internalServerError("Encoding of RelativeTime not implemented.") 
// API doesn't require this.
+    }
+    override fun deserialize(decoder: Decoder): RelativeTime {
+        val jsonInput = decoder as? JsonDecoder ?: throw 
internalServerError("RelativeTime had no JsonDecoder")
+        val json = try {
+            jsonInput.decodeJsonElement().jsonObject
+        } catch (e: Exception) {
+            throw badRequest(e.message) // JSON was malformed.
+        }
+        val maybeDUs = json["d_us"]?.jsonPrimitive ?: throw 
badRequest("Relative time invalid: d_us field not found")
+        if (maybeDUs.isString) {
+            if (maybeDUs.content != "forever") throw badRequest("Only 
'forever' allowed for d_us as string, but '${maybeDUs.content}' was found")
+            return RelativeTime(d_us = Long.MAX_VALUE)
+        }
+        val dUs: Long = maybeDUs.longOrNull ?: throw badRequest("Could not 
convert d_us: '${maybeDUs.content}' to a number")
+        return RelativeTime(d_us = dUs)
+    }
 
-/* Performs the bearer-token authentication.  Returns the
- * authenticated customer on success, null otherwise. */
-fun doTokenAuth(
-    token: String,
-    requiredScope: TokenScope, // readonly or readwrite
-): Customer? {
-    val maybeToken: BearerToken = 
db.bearerTokenGet(token.toByteArray(Charsets.UTF_8)) ?: return null
-    val isExpired: Boolean = maybeToken.expirationTime - getNow().toMicro() < 0
-    if (isExpired || maybeToken.scope != requiredScope) return null // FIXME: 
mention the reason?
-    // Getting the related username.
-    return db.customerGetFromRowId(maybeToken.bankCustomer)
-        ?: throw LibeufinBankException(
-            httpStatus = HttpStatusCode.InternalServerError,
-            talerError = TalerError(
-                code = GENERIC_INTERNAL_INVARIANT_FAILURE,
-                hint = "Customer not found, despite token mentions it.",
-        ))
+    override val descriptor: SerialDescriptor =
+        buildClassSerialDescriptor("RelativeTime") {
+            element<JsonElement>("d_us")
+        }
 }
+@Serializable
+data class TokenRequest(
+    val scope: TokenScope,
+    @Contextual
+    val duration: RelativeTime
+)
+
+class LibeufinBankException(
+    val httpStatus: HttpStatusCode,
+    val talerError: TalerError
+) : Exception(talerError.hint)
 
 /**
  * This function tries to authenticate the call according
@@ -242,6 +243,13 @@ val webApp: Application.() -> Unit = {
         }
     }
     routing {
+        post("/accounts/{USERNAME}/auth-token") {
+            val customer = call.myAuth(TokenScope.readwrite)
+            val endpointOwner = call.expectUriComponent("USERNAME")
+            if (customer == null || customer.login != endpointOwner)
+                throw unauthorized("Auth failed or client has no rights")
+
+        }
         post("/accounts") {
             // check if only admin.
             val maybeOnlyAdmin = db.configGet("only_admin_registrations")
diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt
index b4599cea..384334a6 100644
--- a/bank/src/test/kotlin/JsonTest.kt
+++ b/bank/src/test/kotlin/JsonTest.kt
@@ -1,7 +1,12 @@
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
 import org.junit.Test
+import tech.libeufin.bank.RelativeTime
+import tech.libeufin.bank.RelativeTimeSerializer
+import tech.libeufin.bank.TokenRequest
+import tech.libeufin.bank.TokenScope
 
 @Serializable
 data class MyJsonType(
@@ -22,5 +27,23 @@ class JsonTest {
         """.trimIndent()
         Json.decodeFromString<MyJsonType>(serialized)
     }
-
+    @Test
+    fun unionTypeTest() {
+        val jsonCfg = Json {
+            serializersModule = SerializersModule {
+                contextual(RelativeTime::class) {
+                    RelativeTimeSerializer
+                }
+            }
+        }
+        assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\": 3}").d_us == 
3L)
+        assert(jsonCfg.decodeFromString<RelativeTime>("{\"d_us\": 
\"forever\"}").d_us == Long.MAX_VALUE)
+        val tokenReq = jsonCfg.decodeFromString<TokenRequest>("""
+            {
+              "scope": "readonly",
+              "duration": {"d_us": 30}
+            }
+        """.trimIndent())
+        assert(tokenReq.scope == TokenScope.readonly && tokenReq.duration.d_us 
== 30L)
+    }
 }
\ No newline at end of file

-- 
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]