gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: implement 'demobank' resource


From: gnunet
Subject: [libeufin] branch master updated: implement 'demobank' resource
Date: Tue, 19 Oct 2021 09:20:15 +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 01e4e21  implement 'demobank' resource
01e4e21 is described below

commit 01e4e216098a7c650c45b28c6adf5a0f27f3a1ac
Author: ms <ms@taler.net>
AuthorDate: Tue Oct 19 09:20:04 2021 +0200

    implement 'demobank' resource
---
 .../src/main/kotlin/tech/libeufin/sandbox/Auth.kt  | 47 -------------
 .../src/main/kotlin/tech/libeufin/sandbox/DB.kt    | 67 +++++-------------
 .../main/kotlin/tech/libeufin/sandbox/Helpers.kt   | 20 ++----
 .../src/main/kotlin/tech/libeufin/sandbox/JSON.kt  |  7 ++
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 79 ++++++++++++++--------
 util/src/main/kotlin/Config.kt                     |  8 +--
 util/src/main/kotlin/HTTP.kt                       | 54 ++++++++++++---
 7 files changed, 130 insertions(+), 152 deletions(-)

diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
deleted file mode 100644
index 2ec766e..0000000
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package tech.libeufin.sandbox
-
-import UtilError
-import io.ktor.application.*
-import io.ktor.http.*
-import io.ktor.request.*
-import jdk.jshell.execution.Util
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.slf4j.LoggerFactory
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.LibeufinErrorCode
-import tech.libeufin.util.getHTTPBasicAuthCredentials
-
-private val logger = LoggerFactory.getLogger("tech.libeufin.util")
-/**
- * HTTP basic auth.  Throws error if password is wrong,
- * and makes sure that the user exists in the system.
- *
- * @return user entity
- */
-fun authenticateRequest(request: ApplicationRequest): SandboxUserEntity {
-    return transaction {
-        val (username, password) = getHTTPBasicAuthCredentials(request)
-        val user = SandboxUserEntity.find {
-            SandboxUsersTable.username eq username
-        }.firstOrNull()
-        if (user == null) {
-            throw UtilError(
-                HttpStatusCode.Unauthorized,
-                "Unknown user '$username'",
-                LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
-            )
-        }
-        CryptoUtil.checkPwOrThrow(password, user.passwordHash)
-        user
-    }
-}
-
-fun requireSuperuser(request: ApplicationRequest): SandboxUserEntity {
-    return transaction {
-        val user = authenticateRequest(request)
-        if (!user.superuser) {
-            throw SandboxError(HttpStatusCode.Forbidden, "must be superuser")
-        }
-        user
-    }
-}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 65b9533..fff7d52 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -88,64 +88,33 @@ enum class KeyState {
 }
 
 // FIXME:  This should be DemobankConfigTable!
-object SandboxConfigsTable : LongIdTable() {
+object DemobankConfigsTable : LongIdTable() {
     val currency = text("currency")
     val allowRegistrations = bool("allowRegistrations")
     val bankDebtLimit = integer("bankDebtLimit")
     val usersDebtLimit = integer("usersDebtLimit")
-    val hostname = text("hostname")
+    val name = text("hostname")
 }
 
-class SandboxConfigEntity(id: EntityID<Long>) : LongEntity(id) {
-    companion object : 
LongEntityClass<SandboxConfigEntity>(SandboxConfigsTable)
-    var currency by SandboxConfigsTable.currency
-    var allowRegistrations by SandboxConfigsTable.allowRegistrations
-    var bankDebtLimit by SandboxConfigsTable.bankDebtLimit
-    var usersDebtLimit by SandboxConfigsTable.usersDebtLimit
-    var hostname by SandboxConfigsTable.hostname
+class DemobankConfigEntity(id: EntityID<Long>) : LongEntity(id) {
+    companion object : 
LongEntityClass<DemobankConfigEntity>(DemobankConfigsTable)
+    var currency by DemobankConfigsTable.currency
+    var allowRegistrations by DemobankConfigsTable.allowRegistrations
+    var bankDebtLimit by DemobankConfigsTable.bankDebtLimit
+    var usersDebtLimit by DemobankConfigsTable.usersDebtLimit
+    var name by DemobankConfigsTable.name
 }
 
-/**
- * Currently, this entity is never associated with a bank account,
- * as those get only paired with Ebics subscribers!  Eventually, a
- * Ebics subscriber should map to a SandboxUserEntity that in turn
- * will own bank accounts.
- *
- * FIXME:  Do we really need normal users and superusers for the sandbox?
- * => Nope, we don't even want user management for the sandbox!
- * => This table must be killed, instead we just read the admin token via env 
variable
- *    and use a fixed "admin" user name.
- */
-object SandboxUsersTable : LongIdTable() {
-    val username = text("username")
-    val passwordHash = text("password")
-    val superuser = bool("superuser") // admin
-    /**
-     * Some users may only have an administrative role in the system,
-     * therefore do not need a bank account.
-     */
-    val bankAccount = reference("bankAccount", BankAccountsTable).nullable()
-}
-
-class SandboxUserEntity(id: EntityID<Long>) : LongEntity(id) {
-    companion object : LongEntityClass<SandboxUserEntity>(SandboxUsersTable)
-    var username by SandboxUsersTable.username
-    var passwordHash by SandboxUsersTable.passwordHash
-    var superuser by SandboxUsersTable.superuser
-    var bankAccount by BankAccountEntity optionalReferencedOn 
SandboxUsersTable.bankAccount
-}
-
-
 /**
  * Users who are allowed to log into the demo bank.
  * Created via the /demobanks/{demobankname}/register endpoint.
  */
-object DemobankUsersTable : LongIdTable() {
-    // FIXME: ...
-    // var isPublic = ...
-    // var demobankConfig (=> which demobank is this user part of)
-    // ...
-    // FIXME: Must have a mandatory foreign key reference into 
BankAccountTransactionsTable
+object DemobankCustomersTable : LongIdTable() {
+    val isPublic = bool("isPublic").default(false)
+    val demobankConfig = reference("demobankConfig", DemobankConfigsTable)
+    val balance = text("balance")
+    val username = text("username")
+    val passwordHash = text("passwordHash")
 }
 
 
@@ -479,8 +448,7 @@ fun dbDropTables(dbConnectionString: String) {
             BankAccountsTable,
             BankAccountReportsTable,
             BankAccountStatementsTable,
-            SandboxConfigsTable,
-            SandboxUsersTable,
+            DemobankConfigsTable,
             TalerWithdrawalsTable
         )
     }
@@ -491,8 +459,7 @@ fun dbCreateTables(dbConnectionString: String) {
     TransactionManager.manager.defaultIsolationLevel = 
Connection.TRANSACTION_SERIALIZABLE
     transaction {
         SchemaUtils.create(
-            SandboxConfigsTable,
-            SandboxUsersTable,
+            DemobankConfigsTable,
             EbicsSubscribersTable,
             EbicsHostsTable,
             EbicsDownloadTransactionsTable,
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index bd884ab..ca72be6 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -87,24 +87,16 @@ fun getBankAccountFromSubscriber(subscriber: 
EbicsSubscriberEntity): BankAccount
     }
 }
 
-/**
- * Fetch a configuration for Sandbox, corresponding to the host that runs the 
service.
- */
-fun getSandboxConfig(hostname: String?): SandboxConfigEntity {
-    var ret: SandboxConfigEntity? = transaction {
-        if (hostname == null) {
-            SandboxConfigEntity.all().firstOrNull()
+fun getSandboxConfig(name: String?): DemobankConfigEntity? {
+    return transaction {
+        if (name == null) {
+            DemobankConfigEntity.all().firstOrNull()
         } else {
-            SandboxConfigEntity.find {
-                SandboxConfigsTable.hostname eq hostname
+            DemobankConfigEntity.find {
+                DemobankConfigsTable.name eq name
             }.firstOrNull()
         }
     }
-    if (ret == null) throw SandboxError(
-        HttpStatusCode.InternalServerError,
-        "Serving from a non configured host"
-    )
-    return ret
 }
 
 fun getEbicsSubscriberFromDetails(userID: String, partnerID: String, hostID: 
String): EbicsSubscriberEntity {
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index b890640..8e726cf 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -22,6 +22,13 @@ package tech.libeufin.sandbox
 import tech.libeufin.util.PaymentInfo
 import tech.libeufin.util.RawPayment
 
+data class Demobank(
+    val currency: String,
+    val name: String,
+    val userDebtLimit: Int,
+    val bankDebtLimit: Int,
+    val allowRegistrations: Boolean
+)
 /**
  * Used to show the list of Ebics hosts that exist
  * in the system.
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 59bb89b..4a955f4 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -104,8 +104,8 @@ class Config : CliktCommand("Insert one configuration into 
the database") {
         }
     }
 
-    private val hostnameOption by argument(
-        "HOSTNAME", help = "hostname that serves this configuration"
+    private val nameOption by argument(
+        "NAME", help = "Name of this configuration"
     )
     private val currencyOption by option("--currency").default("EUR")
     private val bankDebtLimitOption by 
option("--bank-debt-limit").int().default(1000000)
@@ -120,12 +120,19 @@ class Config : CliktCommand("Insert one configuration 
into the database") {
         execThrowableOrTerminate {
             dbCreateTables(dbConnString)
             transaction {
-                SandboxConfigEntity.new {
+                val checkExist = DemobankConfigEntity.find {
+                    DemobankConfigsTable.name eq nameOption
+                }.firstOrNull()
+                if (checkExist != null) {
+                    println("Error, demobank ${nameOption} exists already, not 
overriding it.")
+                    exitProcess(1)
+                }
+                DemobankConfigEntity.new {
                     currency = currencyOption
                     bankDebtLimit = bankDebtLimitOption
                     usersDebtLimit = usersDebtLimitOption
                     allowRegistrations = allowRegistrationsOption
-                    hostname = hostnameOption
+                    name = nameOption
                 }
             }
         }
@@ -254,6 +261,11 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
     override fun run() {
         WITH_AUTH = auth
         setLogLevel(logLevel)
+        if (WITH_AUTH && adminPassword == null) {
+            println("Error: auth is enabled, but env 
LIBEUFIN_SANDBOX_ADMIN_PASSWORD is not."
+            + " (Option --no-auth exists for tests)")
+            exitProcess(1)
+        }
         execThrowableOrTerminate { 
dbCreateTables(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)) }
         if (withUnixSocket != null) {
             startServer(
@@ -266,6 +278,15 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
     }
 }
 
+private fun getJsonFromDemobankConfig(fromDb: DemobankConfigEntity): Demobank {
+    return Demobank(
+        currency = fromDb.currency,
+        userDebtLimit = fromDb.usersDebtLimit,
+        bankDebtLimit = fromDb.bankDebtLimit,
+        allowRegistrations = fromDb.allowRegistrations,
+        name = fromDb.name
+    )
+}
 fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?): 
EbicsSubscriberEntity? {
     return if (systemID == null) {
         EbicsSubscriberEntity.find {
@@ -460,7 +481,9 @@ val sandboxApp: Application.() -> Unit = {
             if(adminPassword == null) {
                 throw internalServerError(
                     "Sandbox has no admin password defined." +
-                            " Please define LIBEUFIN_SANDBOX_ADMIN_PASSWORD in 
the environment."
+                            " Please define LIBEUFIN_SANDBOX_ADMIN_PASSWORD in 
the environment, " +
+                            "or launch with --no-auth."
+
                 )
             }
             ac.attributes.put(
@@ -1018,33 +1041,37 @@ val sandboxApp: Application.() -> Unit = {
         // debt limit and possibly other configuration
         // (could also be a CLI command for now)
         post("/demobanks") {
-
+            throw NotImplementedError("Only available in the CLI.")
         }
 
-        // List configured demobanks
         get("/demobanks") {
-
-        }
-
-        delete("/demobank/{demobankid") {
-
+            expectAdmin(call.request.basicAuth())
+            val ret = object { val demoBanks = mutableListOf<Demobank>() }
+            transaction {
+                DemobankConfigEntity.all().forEach {
+                    ret.demoBanks.add(getJsonFromDemobankConfig(it))
+                }
+            }
+            call.respond(ret)
+            return@get
         }
 
-        get("/demobank/{demobankid") {
-
+        get("/demobanks/{demobankid}") {
+            expectAdmin(call.request.basicAuth())
+            val demobankId = call.getUriComponent("demobankid")
+            val ret: DemobankConfigEntity = transaction {
+                DemobankConfigEntity.find {
+                    DemobankConfigsTable.name eq demobankId
+                }.firstOrNull()
+            } ?: throw notFound("Demobank ${demobankId} not found")
+            call.respond(getJsonFromDemobankConfig(ret))
+            return@get
         }
 
-        route("/demobank/{demobankid}") {
-            // Note: Unlike the old pybank, the sandbox does *not* actually 
expose the
-            // taler wire gateway API, because the exchange uses the nexus.
-
-            // Endpoint(s) for making arbitrary payments in the sandbox for 
integration tests
-            // FIXME: Do we actually need this, or can we just use the sandbox 
admin APIs?
-            route("/testing-api") {
-
-            }
+        route("/demobanks/{demobankid}") {
 
             route("/access-api") {
+
                 get("/accounts/{account_name}") {
                     // Authenticated.  Accesses basic information (balance)
                     // about an account. (see docs)
@@ -1068,10 +1095,8 @@ val sandboxApp: Application.() -> Unit = {
                     // Get transaction history of a public account
                 }
 
-                post("/testing/register") {
-                    // Register a new account.
-                    // No authentication is required to register a new user.
-                    // FIXME:  Should probably not use "testing" as the 
prefix, since it's used "in production" in the demobank SPA
+                post("/register") {
+
                 }
             }
 
diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt
index c8af331..11d7156 100644
--- a/util/src/main/kotlin/Config.kt
+++ b/util/src/main/kotlin/Config.kt
@@ -92,10 +92,10 @@ fun getValueFromEnv(varName: String): String? {
 fun getDbConnFromEnv(varName: String): String {
     val dbConnStr = System.getenv(varName)
     if (dbConnStr.isNullOrBlank() or dbConnStr.isNullOrEmpty()) {
-        printLnErr("DB connection string not found/valid in the env variable 
$varName.")
-        printLnErr("The following two examples are valid connection strings:")
-        printLnErr("jdbc:sqlite:/tmp/libeufindb.sqlite3")
-        
printLnErr("jdbc:postgresql://localhost:5432/libeufindb?user=Foo&password=secret")
+        printLnErr("\nError: DB connection string undefined or invalid in the 
env variable $varName.")
+        printLnErr("\nThe following two examples are valid connection 
strings:")
+        printLnErr("\njdbc:sqlite:/tmp/libeufindb.sqlite3")
+        
printLnErr("jdbc:postgresql://localhost:5432/libeufindb?user=Foo&password=secret\n")
         exitProcess(1)
     }
     return dbConnStr
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index b8e4264..52daf78 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -16,7 +16,11 @@ private fun unauthorized(msg: String): UtilError {
     )
 }
 
-
+fun notFound(msg: String): UtilError {
+    return UtilError(
+        HttpStatusCode.NotFound, msg, LibeufinErrorCode.LIBEUFIN_EC_NONE
+    )
+}
 
 /**
  * Returns the token (including the 'secret-token:' prefix)
@@ -48,6 +52,14 @@ fun internalServerError(
     )
 }
 
+fun badRequest(msg: String): UtilError {
+    return UtilError(
+        HttpStatusCode.BadRequest,
+        msg,
+        ec = LibeufinErrorCode.LIBEUFIN_EC_NONE
+    )
+}
+
 /**
  * Get the base URL of a request; handles proxied case.
  */
@@ -81,18 +93,25 @@ fun ApplicationRequest.getBaseUrl(): String {
 }
 
 /**
- * Authenticate the HTTP request with a given token.  This one
- * is expected to comply with the RFC 8959 format; the function
- * throws an exception when the authentication fails
- *
- * @param tokenEnv is the authorization token that was found in the
- * environment.
+ * Get the URI (path's) component or throw Internal server error.
+ * @param component the name of the URI component to return.
+ */
+fun ApplicationCall.getUriComponent(name: String): String {
+    val ret: String? = this.parameters[name]
+    if (ret == null) throw internalServerError("Component $name not found in 
URI")
+    return ret
+}
+/**
+ * Return:
+ * - null if the authentication is disabled (during tests, for example)
+ * - the name of the authenticated user
+ * - throw exception when the authentication fails
  */
-fun ApplicationRequest.basicAuth() {
+fun ApplicationRequest.basicAuth(): String? {
     val withAuth = this.call.ensureAttribute(WITH_AUTH_ATTRIBUTE_KEY)
     if (!withAuth) {
         logger.info("Authentication is disabled - assuming tests currently 
running.")
-        return
+        return null
     }
     val credentials = getHTTPBasicAuthCredentials(this)
     if (credentials.first == "admin") {
@@ -101,7 +120,7 @@ fun ApplicationRequest.basicAuth() {
         if (credentials.second != adminPassword) throw unauthorized(
             "Admin authentication failed"
         )
-        return
+        return credentials.first
     }
     throw unauthorized("Demobank customers not implemented yet!")
     /**
@@ -109,6 +128,20 @@ fun ApplicationRequest.basicAuth() {
      */
 }
 
+/**
+ * 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 (username != "admin") throw unauthorized("Only admin allowed: $username 
is not.")
+}
+
 fun getHTTPBasicAuthCredentials(request: ApplicationRequest): Pair<String, 
String> {
     val authHeader = getAuthorizationHeader(request)
     return extractUserAndPassword(authHeader)
@@ -119,6 +152,7 @@ fun getHTTPBasicAuthCredentials(request: 
ApplicationRequest): Pair<String, Strin
  */
 fun getAuthorizationHeader(request: ApplicationRequest): String {
     val authorization = request.headers["Authorization"]
+    logger.debug("Found Authorization header: $authorization")
     return authorization ?: throw UtilError(
         HttpStatusCode.BadRequest, "Authorization header not found",
         LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED

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