[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: implement 'demobank' resource,
gnunet <=