[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 @@
"settings.editor.splitter.proportion": "0.31419808"
}
}</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=""AmountTest.parseAmountTest"" />
+ <option value=""AmountTest.parseTalerAmountTest"" />
</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=""AmountTest.parseTalerAmountTest"" />
+ <option value=""CryptoUtilTest.passwordHashing"" />
</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=""CryptoUtilTest.passwordHashing"" />
+ <option value=""JsonTest.deserializationTest"" />
</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=""DatabaseTest.bankAccountTest"" />
+ <option value=""JsonTest.unionTypeTest"" />
</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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: RelativeTime parser.,
gnunet <=