gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Remove the UtilError type.


From: gnunet
Subject: [libeufin] branch master updated: Remove the UtilError type.
Date: Fri, 15 Sep 2023 14:13:45 +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 7cc650a5 Remove the UtilError type.
7cc650a5 is described below

commit 7cc650a542901f9e93de322201b3fcc8f8758e44
Author: MS <ms@taler.net>
AuthorDate: Fri Sep 15 14:10:12 2023 +0200

    Remove the UtilError type.
    
    This type used to interfere with Web applications
    responses.  This change makes Util more HTTP agnostic,
    letting Web apps decide how to handle their errors.
    
    Additionally, this change allows testing Util helpers
    without checking the type of the thrown exception.
---
 .gitignore                                      |   2 +-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt |  11 +-
 util/src/main/kotlin/CamtJsonMapping.kt         |  35 ++--
 util/src/main/kotlin/Config.kt                  |  40 +----
 util/src/main/kotlin/CryptoUtil.kt              |  12 --
 util/src/main/kotlin/DB.kt                      |  22 ++-
 util/src/main/kotlin/Errors.kt                  |   9 -
 util/src/main/kotlin/HTTP.kt                    | 220 +++++-------------------
 util/src/main/kotlin/JSON.kt                    |  29 ----
 util/src/main/kotlin/Payto.kt                   |  32 ++--
 util/src/main/kotlin/amounts.kt                 |  27 +--
 util/src/main/kotlin/exec.kt                    |   2 +-
 util/src/main/kotlin/strings.kt                 |  54 +-----
 util/src/main/kotlin/time.kt                    |  48 +-----
 util/src/test/kotlin/AuthTokenTest.kt           |  34 ----
 util/src/test/kotlin/PaytoTest.kt               |  43 +----
 util/src/test/kotlin/TimeTest.kt                |  66 -------
 17 files changed, 107 insertions(+), 579 deletions(-)

diff --git a/.gitignore b/.gitignore
index 182cf6e8..c6c60ce8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.idea
+.idea/
 /nexus/bin/
 /sandbox/bin/
 /util/bin/
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 9b5a9651..e2db8e05 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -60,6 +60,7 @@ const val GENERIC_UNDEFINED = -1 // Filler for ECs that don't 
exist yet.
 
 // TYPES
 
+// FIXME: double-check the enum numeric value.
 enum class FracDigits(howMany: Int) {
     TWO(2),
     EIGHT(8)
@@ -150,8 +151,14 @@ class LibeufinBankException(
  */
 fun ApplicationCall.myAuth(requiredScope: TokenScope): Customer? {
     // Extracting the Authorization header.
-    val header = getAuthorizationRawHeader(this.request)
-    val authDetails = getAuthorizationDetails(header)
+    val header = getAuthorizationRawHeader(this.request) ?: throw badRequest(
+        "Authorization header not found.",
+        GENERIC_HTTP_HEADERS_MALFORMED
+    )
+    val authDetails = getAuthorizationDetails(header) ?: throw badRequest(
+        "Authorization is invalid.",
+        GENERIC_HTTP_HEADERS_MALFORMED
+    )
     return when (authDetails.scheme) {
         "Basic" -> doBasicAuth(authDetails.content)
         "Bearer" -> doTokenAuth(authDetails.content, requiredScope)
diff --git a/util/src/main/kotlin/CamtJsonMapping.kt 
b/util/src/main/kotlin/CamtJsonMapping.kt
index 689b459b..47530760 100644
--- a/util/src/main/kotlin/CamtJsonMapping.kt
+++ b/util/src/main/kotlin/CamtJsonMapping.kt
@@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import com.fasterxml.jackson.databind.annotation.JsonSerialize
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer
 import com.fasterxml.jackson.databind.ser.std.StdSerializer
-import tech.libeufin.util.internalServerError
 
 enum class CreditDebitIndicator {
     DBIT,
@@ -54,10 +53,6 @@ data class CurrencyAmount(
     val value: String
 )
 
-fun CurrencyAmount.toPlainString(): String {
-    return "${this.currency}:${this.value}"
-}
-
 @JsonInclude(JsonInclude.Include.NON_NULL)
 data class CashAccount(
     val name: String?,
@@ -285,14 +280,14 @@ data class CamtBankAccountEntry(
     val batches: List<Batch>?
 ) {
     // Checks that the given list contains only one element and returns it.
-    private fun <T>checkAndGetSingleton(maybeTxs: List<T>?): T {
-        if (maybeTxs == null || maybeTxs.size > 1) throw internalServerError(
-            "Only a singleton transaction is " +
-                    "allowed inside ${this.javaClass}."
-        )
+    private fun <T>checkAndGetSingleton(maybeTxs: List<T>?): T? {
+        if (maybeTxs == null || maybeTxs.size > 1) {
+            logger.error("Only a singleton transaction is allowed inside 
${this.javaClass}.")
+            return null
+        }
         return maybeTxs[0]
     }
-    private fun getSingletonTxDtls(): TransactionDetails {
+    private fun getSingletonTxDtls(): TransactionDetails? {
         /**
          * Types breakdown until the meaningful payment information is reached.
          *
@@ -315,9 +310,9 @@ data class CamtBankAccountEntry(
          * type, that is also -- so far -- required to be a singleton
          * inside Batch.
          */
-        val batch: Batch = checkAndGetSingleton(this.batches)
+        val batch: Batch = checkAndGetSingleton(this.batches) ?: return null
         val batchTransactions = batch.batchTransactions
-        val tx: BatchTransaction = checkAndGetSingleton(batchTransactions)
+        val tx: BatchTransaction = checkAndGetSingleton(batchTransactions) ?: 
return null
         val details: TransactionDetails = tx.details
         return details
     }
@@ -329,18 +324,8 @@ data class CamtBankAccountEntry(
      * and never participate in the application logic.
      */
     @JsonIgnore
-    fun getSingletonSubject(): String {
-        val maybeSubject = 
getSingletonTxDtls().unstructuredRemittanceInformation
-        if (maybeSubject == null) {
-            throw internalServerError(
-                "The parser let in a transaction without subject" +
-                        ", acctSvcrRef: ${this.getSingletonAcctSvcrRef()}."
-            )
-        }
+    fun getSingletonSubject(): String? {
+        val maybeSubject = 
getSingletonTxDtls()?.unstructuredRemittanceInformation ?: return null
         return maybeSubject
     }
-    @JsonIgnore
-    fun getSingletonAcctSvcrRef(): String? {
-        return getSingletonTxDtls().accountServicerRef
-    }
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt
index ac1c636e..62a302a5 100644
--- a/util/src/main/kotlin/Config.kt
+++ b/util/src/main/kotlin/Config.kt
@@ -3,11 +3,8 @@ package tech.libeufin.util
 import ch.qos.logback.classic.Level
 import ch.qos.logback.classic.LoggerContext
 import ch.qos.logback.core.util.Loader
-import io.ktor.server.application.*
 import io.ktor.util.*
 import org.slf4j.LoggerFactory
-import printLnErr
-import kotlin.system.exitProcess
 
 /**
  * Putting those values into the 'attributes' container because they
@@ -42,39 +39,4 @@ fun setLogLevel(logLevel: String?) {
             }
         }
     }
-}
-
-/**
- * Retun the attribute, or throw 500 Internal server error.
- */
-fun <T : Any>ApplicationCall.ensureAttribute(key: AttributeKey<T>): T {
-    if (!this.attributes.contains(key)) {
-        println("Error: attribute $key not found along the call.")
-        throw internalServerError("Attribute $key not found along the call.")
-    }
-    return this.attributes[key]
-}
-
-fun getValueFromEnv(varName: String): String? {
-    val ret = System.getenv(varName)
-    if (ret.isNullOrBlank() or ret.isNullOrEmpty()) {
-        println("WARNING, $varName was not found in the environment. Will stay 
unknown")
-        return null
-    }
-    return ret
-}
-
-// Gets the DB connection string from env, or fail if not found.
-fun getDbConnFromEnv(varName: String): String {
-    val dbConnStr = System.getenv(varName)
-    if (dbConnStr.isNullOrBlank() or dbConnStr.isNullOrEmpty()) {
-        printLnErr("\nError: DB connection string undefined in the env 
variable $varName.")
-        printLnErr("\nThe following two examples are valid connection 
strings:")
-        printLnErr("\npostgres:///libeufindb")
-        
printLnErr("postgresql://localhost:5432/libeufindb?user=Foo&password=secret\n")
-        exitProcess(1)
-    }
-    return dbConnStr
-}
-
-fun getCurrentUser(): String = System.getProperty("user.name")
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/CryptoUtil.kt 
b/util/src/main/kotlin/CryptoUtil.kt
index 97c0bd32..1528c623 100644
--- a/util/src/main/kotlin/CryptoUtil.kt
+++ b/util/src/main/kotlin/CryptoUtil.kt
@@ -19,8 +19,6 @@
 
 package tech.libeufin.util
 
-import UtilError
-import io.ktor.http.*
 import net.taler.wallet.crypto.Base32Crockford
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import java.io.ByteArrayOutputStream
@@ -310,16 +308,6 @@ object CryptoUtil {
         return "sha256-salted\$$salt\$$pwh"
     }
 
-    // Throws error when credentials don't match.  Only returns in case of 
success.
-    fun checkPwOrThrow(pw: String, storedPwHash: String): Boolean {
-        if(!this.checkpw(pw, storedPwHash)) throw UtilError(
-            HttpStatusCode.Unauthorized,
-            "Credentials did not match",
-            LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
-        )
-        return true
-    }
-
     fun checkpw(pw: String, storedPwHash: String): Boolean {
         val components = storedPwHash.split('$')
         if (components.size < 2) {
diff --git a/util/src/main/kotlin/DB.kt b/util/src/main/kotlin/DB.kt
index 667eb1ea..7ae47228 100644
--- a/util/src/main/kotlin/DB.kt
+++ b/util/src/main/kotlin/DB.kt
@@ -31,12 +31,10 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import org.postgresql.jdbc.PgConnection
 import java.net.URI
 
-fun Transaction.isPostgres(): Boolean {
-    return this.db.vendor == "postgresql"
-}
+fun getCurrentUser(): String = System.getProperty("user.name")
 
 fun isPostgres(): Boolean {
-    val db = TransactionManager.defaultDatabase ?: throw internalServerError(
+    val db = TransactionManager.defaultDatabase ?: throw Exception(
         "Could not find the default database, can't check if that's Postgres."
     )
     return db.vendor == "postgresql"
@@ -93,7 +91,7 @@ fun Transaction.postgresNotify(
     if (payload != null) {
         val argEnc = Base32Crockford.encode(payload.toByteArray())
         if (payload.toByteArray().size > 8000)
-            throw internalServerError(
+            throw Exception(
                 "DB notification on channel $channel used >8000 bytes payload 
'$payload'"
             )
         this.exec("NOTIFY $channel, '$argEnc'")
@@ -118,7 +116,7 @@ fun Transaction.postgresNotify(
  * delivery more reliable.
  */
 class PostgresListenHandle(val channelName: String) {
-    private val db = TransactionManager.defaultDatabase ?: throw 
internalServerError(
+    private val db = TransactionManager.defaultDatabase ?: throw Exception(
         "Could not find the default database, won't get Postgres 
notifications."
     )
     private val conn = db.connector().connection as PgConnection
@@ -165,7 +163,7 @@ class PostgresListenHandle(val channelName: String) {
         for (n in maybeNotifications) {
             if (n.name.lowercase() != channelName.lowercase()) {
                 conn.close() // always close on error, without the optional 
check.
-                throw internalServerError("Channel $channelName got notified 
from ${n.name}!")
+                throw Exception("Channel $channelName got notified from 
${n.name}!")
             }
         }
         logger.debug("Found DB notifications on channel $channelName")
@@ -231,7 +229,7 @@ fun getDatabaseName(): String {
                 maybe_db_name = oneLineRes.getString("database_name")
         }
     }
-    return maybe_db_name ?: throw internalServerError("Could not find current 
DB name")
+    return maybe_db_name ?: throw Exception("Could not find current DB name")
 }
 
 /**
@@ -250,7 +248,7 @@ fun connectWithSchema(jdbcConn: String, schemaName: String? 
= null) {
     try { transaction { this.db.name } }
     catch (e: Throwable) {
         logger.error("Test query failed: ${e.message}")
-        throw internalServerError("Failed connection to: $jdbcConn")
+        throw Exception("Failed connection to: $jdbcConn")
     }
 }
 
@@ -266,7 +264,7 @@ fun getJdbcConnectionFromPg(pgConn: String): String {
 fun _getJdbcConnectionFromPg(pgConn: String): String {
     if (!pgConn.startsWith("postgresql://") && 
!pgConn.startsWith("postgres://")) {
         logger.info("Not a Postgres connection string: $pgConn")
-        throw internalServerError("Not a Postgres connection string: $pgConn")
+        throw Exception("Not a Postgres connection string: $pgConn")
     }
     var maybeUnixSocket = false
     val parsed = URI(pgConn)
@@ -293,10 +291,10 @@ fun _getJdbcConnectionFromPg(pgConn: String): String {
         // Check whether the Unix domain socket location was given 
non-standard.
         val socketLocation = hostAsParam ?: "/var/run/postgresql/.s.PGSQL.5432"
         if (!socketLocation.startsWith('/')) {
-            throw internalServerError("PG connection wants Unix domain socket, 
but non-null host doesn't start with slash")
+            throw Exception("PG connection wants Unix domain socket, but 
non-null host doesn't start with slash")
         }
         return 
"jdbc:postgresql://localhost${parsed.path}?user=$pgUser&socketFactory=org.newsclub.net.unix."
 +
                 
"AFUNIXSocketFactory\$FactoryArg&socketFactoryArg=$socketLocation"
     }
     return "jdbc:$pgConn"
-}
\ No newline at end of file
+}
diff --git a/util/src/main/kotlin/Errors.kt b/util/src/main/kotlin/Errors.kt
index e083d327..fa05d708 100644
--- a/util/src/main/kotlin/Errors.kt
+++ b/util/src/main/kotlin/Errors.kt
@@ -25,14 +25,6 @@ import org.slf4j.LoggerFactory
  */
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
-
-open class UtilError(
-    val statusCode: HttpStatusCode,
-    val reason: String,
-    val ec: LibeufinErrorCode? = null
-) :
-    Exception("$reason (HTTP status $statusCode)")
-
 /**
  * Helper function that wraps throwable code and
  * (1) prints the error message and (2) terminates
@@ -53,5 +45,4 @@ fun execThrowableOrTerminate(func: () -> Unit) {
 
 fun printLnErr(errorMessage: String) {
     System.err.println(errorMessage)
-
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index a0aa6765..0f49daa9 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -1,6 +1,5 @@
 package tech.libeufin.util
 
-import UtilError
 import io.ktor.http.*
 import io.ktor.server.application.*
 import io.ktor.server.request.*
@@ -8,113 +7,31 @@ import io.ktor.server.response.*
 import io.ktor.server.util.*
 import io.ktor.util.*
 import logger
-import java.net.URLDecoder
 
-fun unauthorized(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.Unauthorized,
-        msg,
-        LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
-    )
-}
-
-fun notFound(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.NotFound,
-        msg,
-        LibeufinErrorCode.LIBEUFIN_EC_NONE
-    )
-}
-
-fun badGateway(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.BadGateway,
-        msg,
-        LibeufinErrorCode.LIBEUFIN_EC_NONE
-    )
-}
-
-/**
- * Returns the token (including the 'secret-token:' prefix)
- * from an Authorization header.  Throws exception on malformations
- * Note, the token gets URL-decoded before being returned.
- */
-fun extractToken(authHeader: String): String {
-    val headerSplit = authHeader.split(" ", limit = 2)
-    if (headerSplit.elementAtOrNull(0) != "Bearer") throw 
unauthorized("Authorization header does not start with 'Bearer'")
-    val token = headerSplit.elementAtOrNull(1)
-    if (token == null) throw unauthorized("Authorization header did not have 
the token")
-    val tokenSplit = token.split(":", limit = 2)
-    if (tokenSplit.elementAtOrNull(0) != "secret-token")
-        throw unauthorized("Token lacks the 'secret-token:' prefix, see RFC 
8959")
-    val maybeToken = tokenSplit.elementAtOrNull(1)
-    if(maybeToken == null || maybeToken == "")
-        throw unauthorized("Actual token missing after the 'secret-token:' 
prefix")
-    return "${tokenSplit[0]}:${URLDecoder.decode(tokenSplit[1], 
Charsets.UTF_8)}"
-}
-
-fun forbidden(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.Forbidden,
-        msg,
-        ec = LibeufinErrorCode.LIBEUFIN_EC_NONE
-    )
-}
-
-fun nullConfigValueError(
-    configKey: String,
-    demobankName: String = "default"
-): Throwable {
-    return internalServerError("Configuration value for '$configKey' at 
demobank '$demobankName' is null.")
-}
-
-fun internalServerError(
-    reason: String,
-    libeufinErrorCode: LibeufinErrorCode? = LibeufinErrorCode.LIBEUFIN_EC_NONE
-): UtilError {
-    return UtilError(
-        HttpStatusCode.InternalServerError,
-        reason,
-        ec = libeufinErrorCode
-    )
-}
-
-fun badRequest(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.BadRequest,
-        msg,
-        ec = LibeufinErrorCode.LIBEUFIN_EC_NONE
-    )
-}
-
-fun conflict(msg: String): UtilError {
-    return UtilError(
-        HttpStatusCode.Conflict,
-        msg,
-        ec = LibeufinErrorCode.LIBEUFIN_EC_NONE
-    )
-}
-
-/**
- * Get the base URL of a request; handles proxied case.
- */
-fun ApplicationRequest.getBaseUrl(): String {
+// Get the base URL of a request; handles proxied case.
+fun ApplicationRequest.getBaseUrl(): String? {
     return if (this.headers.contains("X-Forwarded-Host")) {
         logger.info("Building X-Forwarded- base URL")
-
         // FIXME: should tolerate a missing X-Forwarded-Prefix.
         var prefix: String = this.headers["X-Forwarded-Prefix"]
-            ?: throw internalServerError("Reverse proxy did not define 
X-Forwarded-Prefix")
+            ?: run {
+                logger.error("Reverse proxy did not define X-Forwarded-Prefix")
+                return null
+            }
         if (!prefix.endsWith("/"))
             prefix += "/"
         URLBuilder(
             protocol = URLProtocol(
-                name = this.headers.get("X-Forwarded-Proto") ?: throw 
internalServerError("Reverse proxy did not define X-Forwarded-Proto"),
+                name = this.headers.get("X-Forwarded-Proto") ?: run {
+                    logger.error("Reverse proxy did not define 
X-Forwarded-Proto")
+                    return null
+                },
                 defaultPort = -1 // Port must be specified with 
X-Forwarded-Host.
             ),
-            host = this.headers.get("X-Forwarded-Host") ?: throw 
internalServerError(
-                "Reverse proxy did not define X-Forwarded-Host"
-            ),
+            host = this.headers.get("X-Forwarded-Host") ?: run {
+                logger.error("Reverse proxy did not define X-Forwarded-Host")
+                return null
+            }
         ).apply {
             encodedPath = prefix
             // Gets dropped otherwise.
@@ -133,43 +50,22 @@ fun ApplicationRequest.getBaseUrl(): String {
  * Get the URI (path's) component or throw Internal server error.
  * @param component the name of the URI component to return.
  */
-fun ApplicationCall.expectUriComponent(name: String): String {
+fun ApplicationCall.expectUriComponent(name: String): String? {
     val ret: String? = this.parameters[name]
-    if (ret == null) throw badRequest("Component $name not found in URI")
-    return ret
-}
-
-/**
- * Throw "unauthorized" if the request is not
- * authenticated by "admin", silently return otherwise.
- *
- * @param username who made the request.
- */
-fun expectAdmin(username: String?) {
-    if (username == null) {
-        logger.info("Skipping 'admin' authentication for tests.")
-        return
+    if (ret == null) {
+        logger.error("Component $name not found in URI")
+        return null
     }
-    if (username != "admin") throw unauthorized("Only admin allowed: $username 
is not.")
-}
-
-fun getHTTPBasicAuthCredentials(request: 
io.ktor.server.request.ApplicationRequest): Pair<String, String> {
-    val authHeader = getAuthorizationRawHeader(request)
-    return extractUserAndPassword(authHeader)
+    return ret
 }
 
-// Extracts the Authorization:-header line and throws error if not found.
-fun getAuthorizationRawHeader(request: ApplicationRequest): String {
+// Extracts the Authorization:-header line, or returns null if not found.
+fun getAuthorizationRawHeader(request: ApplicationRequest): String? {
     val authorization = request.headers["Authorization"]
-    return authorization ?: throw badRequest("Authorization header not found")
-}
-
-// Builds the Authorization:-header value, given the credentials.
-fun buildBasicAuthLine(username: String, password: String): String {
-    val ret = "Basic "
-    val cred = "$username:$password"
-    val enc = bytesToBase64(cred.toByteArray(Charsets.UTF_8))
-    return ret+enc
+    return authorization ?: run {
+        logger.error("Authorization header not found")
+        return null
+    }
 }
 
 /**
@@ -181,64 +77,28 @@ data class AuthorizationDetails(
     val scheme: String,
     val content: String
 )
-// Returns the authorization scheme mentioned in the Auth header.
-fun getAuthorizationDetails(authorizationHeader: String): AuthorizationDetails 
{
+// Returns the authorization scheme mentioned in the Auth header,
+// or null if that could not be found.
+fun getAuthorizationDetails(authorizationHeader: String): 
AuthorizationDetails? {
     val split = authorizationHeader.split(" ")
-    if (split.isEmpty()) throw badRequest("malformed Authorization header: 
contains no space")
-    if (split.size != 2) throw badRequest("malformed Authorization header: 
contains more than one space")
-    return AuthorizationDetails(scheme = split[0], content = split[1])
-}
-
-/**
- * This helper function parses a Authorization:-header line, decode the 
credentials
- * and returns a pair made of username and hashed (sha256) password.  The 
hashed value
- * will then be compared with the one kept into the database.
- */
-fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
-    val (username, password) = try {
-        // FIXME/note: line below doesn't check for "Basic" presence.
-        val split = authorizationHeader.split(" ")
-        val plainUserAndPass = String(base64ToBytes(split[1]), Charsets.UTF_8)
-        val ret = plainUserAndPass.split(":", limit = 2)
-        if (ret.size < 2) throw java.lang.Exception(
-            "HTTP Basic auth line does not contain username and password"
-        )
-        ret
-    } catch (e: Exception) {
-        throw UtilError(
-            HttpStatusCode.BadRequest,
-            "invalid Authorization header received: ${e.message}",
-            LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
-        )
-    }
-    return Pair(username, password)
-}
-
-fun expectInt(uriParam: String): Int {
-    return try { Integer.decode(uriParam) }
-    catch (e: Exception) {
-        logger.error(e.message)
-        throw badRequest("'$uriParam' is not Int")
+    if (split.isEmpty()) {
+        logger.error("malformed Authorization header: contains no space")
+        return null
     }
-}
-fun expectLong(uriParam: String): Long {
-    return try { uriParam.toLong() }
-    catch (e: Exception) {
-        logger.error(e.message)
-        throw badRequest("'$uriParam' is not Long")
+    if (split.size != 2) {
+        logger.error("malformed Authorization header: contains more than one 
space")
+        return null
     }
+    return AuthorizationDetails(scheme = split[0], content = split[1])
 }
 
-// Returns null, or tries to convert the parameter to type T.
-// Throws Bad Request, if the conversion could not be done.
+// Gets a long from the URI param named 'uriParamName',
+// or null if that is not found.
 fun ApplicationCall.maybeLong(uriParamName: String): Long? {
     val maybeParam = this.parameters[uriParamName] ?: return null
     return try { maybeParam.toLong() }
     catch (e: Exception) {
-        throw badRequest("Could not convert '$uriParamName' to Long")
+        logger.error("Could not convert '$uriParamName' to Long")
+        return null
     }
-}
-
-// Join base URL and path ensuring one (and only one) slash in between.
-fun joinUrl(baseUrl: String, path: String): String =
-    baseUrl.dropLastWhile { it == '/' } + '/' + path.dropWhile { it == '/' }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/JSON.kt b/util/src/main/kotlin/JSON.kt
index 2be1f3d0..03c25dde 100644
--- a/util/src/main/kotlin/JSON.kt
+++ b/util/src/main/kotlin/JSON.kt
@@ -22,35 +22,6 @@ package tech.libeufin.util
 enum class XLibeufinBankDirection(val direction: String) {
     DEBIT("debit"),
     CREDIT("credit");
-    companion object {
-        fun parseXLibeufinDirection(direction: String): XLibeufinBankDirection 
{
-            return when(direction) {
-                "credit" -> CREDIT
-                "debit" -> DEBIT
-                else -> throw internalServerError(
-                    "Cannot extract ${this::class.java.typeName}' instance 
from value: $direction'"
-                )
-            }
-        }
-
-        /**
-         * Sandbox uses _some_ CaMt terminology even for its internal
-         * data model.  This function helps to bridge such CaMt terminology
-         * to the Sandbox simplified JSON format (XLibeufinBankTransaction).
-         *
-         * Ideally, the terminology should be made more abstract to serve
-         * both (and probably more) data formats.
-         */
-        fun convertCamtDirectionToXLibeufin(camtDirection: String): 
XLibeufinBankDirection {
-            return when(camtDirection) {
-                "CRDT" -> CREDIT
-                "DBIT" -> DEBIT
-                else -> throw internalServerError(
-                    "Cannot extract ${this::class.java.typeName}' instance 
from value: $camtDirection'"
-                )
-            }
-        }
-    }
     fun exportAsCamtDirection(): String =
         when(this) {
             CREDIT -> "CRDT"
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/Payto.kt
index 3b54060c..026aecc3 100644
--- a/util/src/main/kotlin/Payto.kt
+++ b/util/src/main/kotlin/Payto.kt
@@ -1,11 +1,10 @@
 package tech.libeufin.util
 
-import UtilError
 import io.ktor.http.*
+import logger
 import java.net.URI
 import java.net.URLDecoder
 import java.net.URLEncoder
-import javax.security.auth.Subject
 
 // Payto information.
 data class Payto(
@@ -17,10 +16,9 @@ data class Payto(
     val message: String?,
     val amount: String?
 )
-class InvalidPaytoError(msg: String) : UtilError(HttpStatusCode.BadRequest, 
msg)
 
 // Return the value of query string parameter 'name', or null if not found.
-// 'params' is the a list of key-value elements of all the query parameters 
found in the URI.
+// 'params' is the list of key-value elements of all the query parameters 
found in the URI.
 private fun getQueryParamOrNull(name: String, params: List<Pair<String, 
String>>?): String? {
     if (params == null) return null
     return params.firstNotNullOfOrNull { pair ->
@@ -28,30 +26,37 @@ private fun getQueryParamOrNull(name: String, params: 
List<Pair<String, String>>
     }
 }
 
-fun parsePayto(payto: String): Payto {
+// Parses a Payto URI, returning null if the input is invalid.
+fun parsePayto(payto: String): Payto? {
     /**
      * This check is due because URIs having a "payto:" prefix without
      * slashes are correctly parsed by the Java 'URI' class.  'mailto'
      * for example lacks the double-slash part.
      */
-    if (!payto.startsWith("payto://"))
-        throw InvalidPaytoError("Invalid payto URI: $payto")
+    if (!payto.startsWith("payto://")) {
+        logger.error("Invalid payto URI: $payto")
+        return null
+    }
 
     val javaParsedUri = try {
         URI(payto)
     } catch (e: java.lang.Exception) {
-        throw InvalidPaytoError("'${payto}' is not a valid URI")
+        logger.error("'${payto}' is not a valid URI")
+        return null
     }
     if (javaParsedUri.scheme != "payto") {
-        throw InvalidPaytoError("'${payto}' is not payto")
+        logger.error("'${payto}' is not payto")
+        return null
     }
     val wireMethod = javaParsedUri.host
     if (wireMethod != "iban") {
-        throw InvalidPaytoError("Only 'iban' is supported, not '$wireMethod'")
+        logger.error("Only 'iban' is supported, not '$wireMethod'")
+        return null
     }
     val splitPath = javaParsedUri.path.split("/").filter { it.isNotEmpty() }
     if (splitPath.size > 2) {
-        throw InvalidPaytoError("too many path segments in iban payto URI: 
$payto")
+        logger.error("too many path segments in iban payto URI: $payto")
+        return null
     }
     val (iban, bic) = if (splitPath.size == 1) {
         Pair(splitPath[0], null)
@@ -61,7 +66,10 @@ fun parsePayto(payto: String): Payto {
         val queryString: List<String> = javaParsedUri.query.split("&")
         queryString.map {
             val split = it.split("=");
-            if (split.size != 2) throw InvalidPaytoError("parameter '$it' was 
malformed")
+            if (split.size != 2) {
+                logger.error("parameter '$it' was malformed")
+                return null
+            }
             Pair(split[0], split[1])
         }
     } else null
diff --git a/util/src/main/kotlin/amounts.kt b/util/src/main/kotlin/amounts.kt
index 671dfbd3..3f145caa 100644
--- a/util/src/main/kotlin/amounts.kt
+++ b/util/src/main/kotlin/amounts.kt
@@ -1,7 +1,5 @@
 package tech.libeufin.util
 
-import UtilError
-import io.ktor.http.*
 import java.math.BigDecimal
 import java.math.RoundingMode
 
@@ -28,31 +26,8 @@ const val plainAmountRe = "^([0-9]+(\\.[0-9][0-9]?)?)$"
 const val plainAmountReWithSign = "^-?([0-9]+(\\.[0-9][0-9]?)?)$"
 const val amountWithCurrencyRe = "^([A-Z]+):([0-9]+(\\.[0-9][0-9]?)?)$"
 
-// Ensures that the number part of one amount matches the allowed format.
-// Currently, at most two fractional digits are allowed.  It returns true
-// in the matching case, false otherwise.
-fun validatePlainAmount(plainAmount: String, withSign: Boolean = false): 
Boolean {
-    if (withSign) return Regex(plainAmountReWithSign).matches(plainAmount)
-    return Regex(plainAmountRe).matches(plainAmount)
-}
-
-fun parseAmount(amount: String): AmountWithCurrency {
-    val match = Regex(amountWithCurrencyRe).find(amount) ?:
-        throw UtilError(HttpStatusCode.BadRequest, "invalid amount: $amount")
-    val (currency, number) = match.destructured
-    return AmountWithCurrency(currency = currency, amount = number)
-}
-
-fun isAmountZero(a: BigDecimal): Boolean {
-    a.abs().toPlainString().forEach {
-        if (it != '0' && it != '.')
-            return false
-    }
-    return true
-}
-
 fun BigDecimal.roundToTwoDigits(): BigDecimal {
     // val twoDigitsRounding = MathContext(2)
     // return this.round(twoDigitsRounding)
     return this.setScale(2, RoundingMode.HALF_UP)
-}
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/exec.kt b/util/src/main/kotlin/exec.kt
index 653110e8..e4cfd1df 100644
--- a/util/src/main/kotlin/exec.kt
+++ b/util/src/main/kotlin/exec.kt
@@ -31,6 +31,6 @@ fun execCommand(cmd: List<String>, throwIfFails: Boolean = 
true): Int {
         .start()
         .waitFor()
     if (result != 0 && throwIfFails)
-        throw internalServerError("Command '$cmd' failed.")
+        throw Exception("Command '$cmd' failed.")
     return result
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt
index 563afe34..0490b209 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/util/src/main/kotlin/strings.kt
@@ -19,10 +19,8 @@
 
 package tech.libeufin.util
 
-import UtilError
-import io.ktor.http.HttpStatusCode
+import logger
 import java.math.BigInteger
-import java.math.BigDecimal
 import java.util.*
 
 fun ByteArray.toHexString(): String {
@@ -103,24 +101,6 @@ data class AmountWithCurrency(
     val amount: String
 )
 
-fun parseDecimal(decimalStr: String): BigDecimal {
-    if(!validatePlainAmount(decimalStr, withSign = true))
-        throw UtilError(
-            HttpStatusCode.BadRequest,
-            "Bad string amount given: $decimalStr",
-            LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
-        )
-    return try {
-        BigDecimal(decimalStr)
-    } catch (e: NumberFormatException) {
-        throw UtilError(
-            HttpStatusCode.BadRequest,
-            "Bad string amount given: $decimalStr",
-            LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
-        )
-    }
-}
-
 fun getRandomString(length: Int): String {
     val allowedChars = ('A' .. 'Z') + ('0' .. '9')
     return (1 .. length)
@@ -146,31 +126,7 @@ fun isValidResourceName(name: String): Boolean {
     return name.matches(Regex("[a-z]([-a-z0-9]*[a-z0-9])?"))
 }
 
-fun requireValidResourceName(name: String): String {
-    if (!isValidResourceName(name)) {
-        throw UtilError(
-            HttpStatusCode.BadRequest,
-            "Invalid resource name. The first character must be a lowercase 
letter, " +
-                    "and all following characters (except for the last 
character) must be a dash, " +
-                    "lowercase letter, or digit. The last character must be a 
lowercase letter or digit.",
-            LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
-        )
-    }
-    return name
-}
-
-
-fun sanityCheckOrThrow(credentials: Pair<String, String>) {
-    if (!sanityCheckCredentials(credentials)) throw UtilError(
-        HttpStatusCode.BadRequest,
-        "Please only use alphanumeric credentials.",
-        LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
-    )
-}
-
-/**
- * Sanity-check user's credentials.
- */
+// Sanity-check user's credentials.
 fun sanityCheckCredentials(credentials: Pair<String, String>): Boolean {
     val allowedChars = Regex("^[a-zA-Z0-9]+$")
     if (!allowedChars.matches(credentials.first)) return false
@@ -182,11 +138,12 @@ fun sanityCheckCredentials(credentials: Pair<String, 
String>): Boolean {
  * Parses string into java.util.UUID format or throws 400 Bad Request.
  * The output is usually consumed in database queries.
  */
-fun parseUuid(maybeUuid: String): UUID {
+fun parseUuid(maybeUuid: String): UUID? {
     val uuid = try {
         UUID.fromString(maybeUuid)
     } catch (e: Exception) {
-        throw badRequest("'$maybeUuid' is an invalid UUID.")
+        logger.error("'$maybeUuid' is an invalid UUID.")
+        return null
     }
     return uuid
 }
@@ -198,6 +155,7 @@ fun hasWopidPlaceholder(captchaUrl: String): Boolean {
 }
 
 // Tries to extract a valid reserve public key from the raw subject line
+// or returns null if the input is invalid.
 fun extractReservePubFromSubject(rawSubject: String): String? {
     val re = "\\b[a-z0-9A-Z]{52}\\b".toRegex()
     val result = re.find(rawSubject.replace("[\n]+".toRegex(), "")) ?: return 
null
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
index ff75158d..3589dec2 100644
--- a/util/src/main/kotlin/time.kt
+++ b/util/src/main/kotlin/time.kt
@@ -22,54 +22,8 @@ package tech.libeufin.util
 import java.time.*
 import java.time.format.DateTimeFormatter
 
-private var LIBEUFIN_CLOCK = Clock.system(ZoneId.systemDefault())
-
-fun setClock(rel: Duration) {
-    LIBEUFIN_CLOCK = Clock.offset(LIBEUFIN_CLOCK, rel)
-}
 fun getNow(): ZonedDateTime {
     return ZonedDateTime.now(ZoneId.systemDefault())
 }
 
-fun ZonedDateTime.toMicro(): Long = this.nano / 1000L
-fun getNowMillis(): Long = getNow().toInstant().toEpochMilli()
-
-fun getSystemTimeNow(): ZonedDateTime {
-    // return ZonedDateTime.now(ZoneOffset.UTC)
-    return ZonedDateTime.now(ZoneId.systemDefault())
-}
-
-fun ZonedDateTime.toZonedString(): String {
-    return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(this)
-}
-
-fun ZonedDateTime.toDashedDate(): String {
-    return DateTimeFormatter.ISO_DATE.format(this)
-}
-
-fun importDateFromMillis(millis: Long): ZonedDateTime {
-    return ZonedDateTime.ofInstant(
-        Instant.ofEpochMilli(millis),
-        ZoneOffset.UTC
-    )
-}
-
-fun LocalDateTime.millis(): Long {
-    val instant = Instant.from(this.atZone(ZoneOffset.UTC))
-    return instant.toEpochMilli()
-}
-
-fun LocalDate.millis(): Long {
-    val instant = 
Instant.from(this.atStartOfDay().atZone(ZoneId.systemDefault()))
-    return instant.toEpochMilli()
-}
-
-fun parseDashedDate(maybeDashedDate: String?): LocalDate {
-    if (maybeDashedDate == null)
-        throw badRequest("dashed date found as null")
-    return try {
-        LocalDate.parse(maybeDashedDate)
-    } catch (e: Exception) {
-        throw badRequest("bad dashed date: $maybeDashedDate.  ${e.message}")
-    }
-}
\ No newline at end of file
+fun ZonedDateTime.toMicro(): Long = this.nano / 1000L
\ No newline at end of file
diff --git a/util/src/test/kotlin/AuthTokenTest.kt 
b/util/src/test/kotlin/AuthTokenTest.kt
deleted file mode 100644
index cf4c8ebf..00000000
--- a/util/src/test/kotlin/AuthTokenTest.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-import org.junit.Test
-import tech.libeufin.util.extractToken
-import java.lang.Exception
-
-class AuthTokenTest {
-    @Test
-    fun test() {
-        val tok = extractToken("Bearer secret-token:XXX")
-        assert(tok == "secret-token:XXX")
-        val tok_0 = extractToken("Bearer secret-token:XXX%20YYY")
-        assert(tok_0 == "secret-token:XXX YYY")
-        val tok_1 = extractToken("Bearer secret-token:XXX YYY")
-        assert(tok_1 == "secret-token:XXX YYY")
-        val tok_2 = extractToken("Bearer secret-token:XXX ")
-        assert(tok_2 == "secret-token:XXX ")
-
-        val malformedAuths = listOf(
-            "", "XXX", "Bearer", "Bearer ", "Bearer XXX",
-            "BearerXXX", "XXXBearer", "Bearer secret-token",
-            "Bearer secret-token:", " Bearer", " Bearer secret-token:XXX",
-            ":: ::"
-        )
-        for (token in malformedAuths) {
-            try {
-                extractToken(token)
-            } catch (e: Exception) {
-                assert(e is UtilError)
-                continue
-            }
-            println("Error: '$token' made it through")
-            assert(false) // should never arrive here.
-        }
-    }
-}
\ No newline at end of file
diff --git a/util/src/test/kotlin/PaytoTest.kt 
b/util/src/test/kotlin/PaytoTest.kt
index 88c14fa1..c7174883 100644
--- a/util/src/test/kotlin/PaytoTest.kt
+++ b/util/src/test/kotlin/PaytoTest.kt
@@ -1,58 +1,29 @@
 import org.junit.Test
-import tech.libeufin.util.InvalidPaytoError
+import tech.libeufin.util.Payto
 import tech.libeufin.util.parsePayto
 
 class PaytoTest {
 
     @Test
     fun wrongCases() {
-        try {
-            parsePayto("payto://iban/IBAN/BIC")
-        } catch (e: InvalidPaytoError) {
-            println(e)
-            println("must give IBAN _and_ BIC")
-        }
-        try {
-            parsePayto("http://iban/BIC123/IBAN123?receiver-name=The%20Name";)
-        } catch (e: InvalidPaytoError) {
-            println(e)
-            println("wrong scheme was caught")
-        }
-        try {
-            parsePayto(
-                
"payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house"
-            )
-        } catch (e: InvalidPaytoError) {
-            println(e)
-            println("'://' missing, invalid Payto")
-        }
-        try {
-            
parsePayto("payto://iban/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
-        } catch (e: InvalidPaytoError) {
-            println(e)
-        }
-        try {
-            
parsePayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
-        } catch (e: InvalidPaytoError) {
-            println(e)
-        }
+        
assert(parsePayto("http://iban/BIC123/IBAN123?receiver-name=The%20Name";) == 
null)
+        
assert(parsePayto("payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house")
 == null)
+        
assert(parsePayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
 == null)
     }
 
     @Test
     fun parsePaytoTest() {
-        val withBic = 
parsePayto("payto://iban/BIC123/IBAN123?receiver-name=The%20Name")
+        val withBic: Payto = 
parsePayto("payto://iban/BIC123/IBAN123?receiver-name=The%20Name")!!
         assert(withBic.iban == "IBAN123")
         assert(withBic.bic == "BIC123")
         assert(withBic.receiverName == "The Name")
-        val complete = 
parsePayto("payto://iban/BIC123/IBAN123?sender-name=The%20Name&amount=EUR:1&message=donation")
+        val complete = 
parsePayto("payto://iban/BIC123/IBAN123?sender-name=The%20Name&amount=EUR:1&message=donation")!!
         assert(withBic.iban == "IBAN123")
         assert(withBic.bic == "BIC123")
         assert(withBic.receiverName == "The Name")
         assert(complete.message == "donation")
         assert(complete.amount == "EUR:1")
-        val withoutOptionals = parsePayto(
-            "payto://iban/IBAN123"
-        )
+        val withoutOptionals = parsePayto("payto://iban/IBAN123")!!
         assert(withoutOptionals.bic == null)
         assert(withoutOptionals.message == null)
         assert(withoutOptionals.receiverName == null)
diff --git a/util/src/test/kotlin/TimeTest.kt b/util/src/test/kotlin/TimeTest.kt
deleted file mode 100644
index 76243f33..00000000
--- a/util/src/test/kotlin/TimeTest.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-import org.junit.Ignore
-import org.junit.Test
-import tech.libeufin.util.getNow
-import tech.libeufin.util.millis
-import tech.libeufin.util.setClock
-import java.time.*
-import java.time.format.DateTimeFormatter
-
-// 
https://stackoverflow.com/questions/32437550/whats-the-difference-between-instant-and-localdatetime
-
-// Ignoring because no assert takes place here.
-@Ignore
-class TimeTest {
-    @Test
-    fun mock() {
-        println(getNow())
-        setClock(Duration.ofHours(2))
-        println(getNow())
-    }
-
-    @Test
-    fun importMillis() {
-        fun fromLong(millis: Long): LocalDateTime {
-            return LocalDateTime.ofInstant(
-                Instant.ofEpochMilli(millis),
-                ZoneId.systemDefault()
-            )
-        }
-        val ret = fromLong(0)
-        println(ret.toString())
-    }
-
-    @Test
-    fun printLong() {
-        val l = 1111111L
-        println(l.javaClass)
-        println(l.toString())
-    }
-
-    @Test
-    fun formatDateTime() {
-        fun formatDashed(dateTime: LocalDateTime): String {
-            val dtf = DateTimeFormatter.ISO_LOCAL_DATE
-            return dtf.format(dateTime)
-        }
-        fun formatZonedWithOffset(dateTime: ZonedDateTime): String {
-            val dtf = DateTimeFormatter.ISO_OFFSET_DATE_TIME
-            return dtf.format(dateTime)
-        }
-        val str = formatDashed(LocalDateTime.now())
-        println(str)
-        val str0 = 
formatZonedWithOffset(LocalDateTime.now().atZone(ZoneId.systemDefault()))
-        println(str0)
-    }
-
-    @Test
-    fun parseDashedDate() {
-        fun parse(dashedDate: String): LocalDate {
-            val dtf = DateTimeFormatter.ISO_LOCAL_DATE
-            return LocalDate.parse(dashedDate, dtf)
-        }
-        val ret: LocalDate = parse("1970-01-01")
-        println(ret.toString())
-        ret.millis() // Just testing it doesn't raise Exception.
-    }
-}
\ 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]