gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (5e1baab1 -> b556bd1b)


From: gnunet
Subject: [libeufin] branch master updated (5e1baab1 -> b556bd1b)
Date: Mon, 05 Dec 2022 20:42:58 +0100

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository libeufin.

    from 5e1baab1 comments
     new b33d4324 Some legacy API tests.
     new b556bd1b Legacy API access control.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 nexus/build.gradle                                 |  13 +-
 .../tech/libeufin/nexus/ebics/EbicsClient.kt       |   1 -
 nexus/src/test/kotlin/DownloadAndSubmit.kt         |   4 +-
 nexus/src/test/kotlin/SandboxLegacyApiTest.kt      | 304 +++++++++++++++++++++
 nexus/src/test/kotlin/SelfContainedDBTest.kt       |  72 -----
 .../main/kotlin/tech/libeufin/sandbox/Helpers.kt   |  41 ++-
 .../src/main/kotlin/tech/libeufin/sandbox/JSON.kt  |  10 +
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 145 ++++++----
 8 files changed, 453 insertions(+), 137 deletions(-)
 create mode 100644 nexus/src/test/kotlin/SandboxLegacyApiTest.kt
 delete mode 100644 nexus/src/test/kotlin/SelfContainedDBTest.kt

diff --git a/nexus/build.gradle b/nexus/build.gradle
index e5a0affb..c5009588 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -89,6 +89,7 @@ dependencies {
     // Ktor, an HTTP client and server library
     implementation "io.ktor:ktor-server-core:$ktor_version"
     implementation "io.ktor:ktor-client-apache:$ktor_version"
+    implementation "io.ktor:ktor-client-auth:$ktor_version"
     implementation "io.ktor:ktor-server-netty:$ktor_version"
     implementation "io.ktor:ktor-server-test-host:$ktor_version"
     implementation "io.ktor:ktor-auth:$ktor_version"
@@ -101,18 +102,26 @@ dependencies {
     implementation 'com.cronutils:cron-utils:9.1.5'
 
     // Unit testing
-    testImplementation 'junit:junit:4.13.2'
+    // testImplementation 'junit:junit:4.13.2'
+    // From 
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics:
+    testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
     testImplementation 'org.jetbrains.kotlin:kotlin-test:1.5.21'
     testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.5.21'
 }
 
+test {
+    useJUnit()
+    failFast = true
+    testLogging.showStandardStreams = false
+    environment.put("LIBEUFIN_SANDBOX_ADMIN_PASSWORD", "foo")
+}
+
 application {
     mainClassName = "tech.libeufin.nexus.MainKt"
     applicationName = "libeufin-nexus"
     applicationDefaultJvmArgs = ['-Djava.net.preferIPv6Addresses=true']
 }
 
-
 jar {
     manifest {
         attributes "Main-Class": "tech.libeufin.nexus.MainKt"
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
index 95f6f5ec..bc2233b1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
@@ -35,7 +35,6 @@ import java.util.*
 private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
 
 private suspend inline fun HttpClient.postToBank(url: String, body: String): 
String {
-    // logger.debug("Posting: $body")
     if (!XMLUtil.validateFromString(body)) throw NexusError(
         HttpStatusCode.InternalServerError,
         "EBICS (outgoing) document is invalid"
diff --git a/nexus/src/test/kotlin/DownloadAndSubmit.kt 
b/nexus/src/test/kotlin/DownloadAndSubmit.kt
index 84832bd9..692ac55b 100644
--- a/nexus/src/test/kotlin/DownloadAndSubmit.kt
+++ b/nexus/src/test/kotlin/DownloadAndSubmit.kt
@@ -187,8 +187,8 @@ class DownloadAndSubmit {
                     val painMessage = createPain001document(
                         NexusPaymentInitiationData(
                             debtorIban = bar!!.iban,
-                            debtorBic = bar!!.bankCode,
-                            debtorName = bar!!.accountHolder,
+                            debtorBic = bar.bankCode,
+                            debtorName = bar.accountHolder,
                             currency = "TESTKUDOS",
                             amount = "1",
                             creditorIban = getIban(),
diff --git a/nexus/src/test/kotlin/SandboxLegacyApiTest.kt 
b/nexus/src/test/kotlin/SandboxLegacyApiTest.kt
new file mode 100644
index 00000000..b307e744
--- /dev/null
+++ b/nexus/src/test/kotlin/SandboxLegacyApiTest.kt
@@ -0,0 +1,304 @@
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream
+import io.ktor.client.features.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.HttpResponse
+import io.ktor.http.*
+import io.ktor.server.testing.*
+import io.ktor.util.*
+import io.ktor.utils.io.*
+import io.netty.handler.codec.http.HttpResponseStatus
+import kotlinx.coroutines.runBlocking
+import org.junit.Ignore
+import org.junit.Test
+import tech.libeufin.sandbox.sandboxApp
+import tech.libeufin.util.buildBasicAuthLine
+import tech.libeufin.util.getIban
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.nio.ByteBuffer
+
+/**
+ * Mostly checking legacy API's access control.
+ */
+class SandboxLegacyApiTest {
+    fun dbHelper (f: () -> Unit) {
+        withTestDatabase {
+            prepSandboxDb()
+            f()
+        }
+    }
+    val mapper = ObjectMapper()
+
+
+    // EBICS Subscribers API.
+    @Test
+    fun adminEbiscSubscribers() {
+        dbHelper {
+            withTestApplication(sandboxApp) {
+                runBlocking {
+                    /**
+                     * Create a EBICS subscriber.  That conflicts because
+                     * MakeEnv.kt created it already, but tests access control
+                     * and conflict detection.
+                     */
+                    var body = mapper.writeValueAsString(object {
+                        val hostID = "foo"
+                        val userID = "foo"
+                        val systemID = "foo"
+                        val partnerID = "foo"
+                    })
+                    var r: HttpResponse = 
client.post("/admin/ebics/subscribers") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                            append(
+                                HttpHeaders.ContentType,
+                                ContentType.Application.Json
+                            )
+                        }
+                        this.body = body
+                    }
+                    assert(r.status.value == HttpStatusCode.Conflict.value)
+                    /**
+                     * Check that EBICS subscriber indeed exists.
+                     */
+                    r = client.get("/admin/ebics/subscribers") {
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.OK.value)
+                    val buf = ByteArrayOutputStream()
+                    r.content.read { buf.write(it.array()) }
+                    val respObj = mapper.readTree(buf.toString())
+                    assert("foo" == 
respObj.get("subscribers").get(0).get("userID").asText())
+                    /**
+                     * Try same operations as above, with wrong admin 
credentials
+                     */
+                    r = client.get("/admin/ebics/subscribers") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "wrong")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                    r = client.post("/admin/ebics/subscribers") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "wrong")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                    // Good credentials, but unauthorized user.
+                    r = client.get("/admin/ebics/subscribers") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("foo", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                    r = client.post("/admin/ebics/subscribers") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("foo", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                    /**
+                     * Give a bank account to the existing subscriber.  Bank 
account
+                     * is (implicitly / hard-coded) hosted at default demobank.
+                     */
+                    // Create new subscriber.  No need to have the related 
customer.
+                    body = mapper.writeValueAsString(object {
+                        val hostID = "eufinSandbox"
+                        val userID = "baz"
+                        val partnerID = "baz"
+                        val systemID = "foo"
+                    })
+                    client.post<HttpResponse>("/admin/ebics/subscribers") {
+                        expectSuccess = true
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                            append(
+                                HttpHeaders.ContentType,
+                                ContentType.Application.Json
+                            )
+                        }
+                        this.body = body
+                    }
+                    // Associate new bank account to it.
+                    body = mapper.writeValueAsString(object {
+                        val subscriber = object {
+                            val userID = "baz"
+                            val partnerID = "baz"
+                            val systemID = "baz"
+                            val hostID = "eufinSandbox"
+                        }
+                        val iban = getIban()
+                        val bic = "SANDBOXX"
+                        val name = "Now Have Account"
+                        val label = "baz"
+                        val owner = "baz"
+                    })
+                    client.post<HttpResponse>("/admin/ebics/bank-accounts") {
+                        expectSuccess = true
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                            append(
+                                HttpHeaders.ContentType,
+                                ContentType.Application.Json
+                            )
+                        }
+                        this.body = body
+                    }
+                    r = client.get("/admin/ebics/subscribers") {
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.OK.value)
+                    val buf_ = ByteArrayOutputStream()
+                    r.content.read { buf_.write(it.array()) }
+                    val respObj_ = mapper.readTree(buf_.toString())
+                    val bankAccountLabel = 
respObj_.get("subscribers").get(1).get("demobankAccountLabel").asText()
+                    assert("baz" == bankAccountLabel)
+                    // Same operation, wrong/unauth credentials.
+                    r = client.post("/admin/ebics/bank-accounts") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "wrong")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                    r = client.post("/admin/ebics/bank-accounts") {
+                        expectSuccess = false
+                        headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("foo", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpStatusCode.Unauthorized.value)
+                }
+            }
+        }
+    }
+
+    // EBICS Hosts API.
+    @Ignore
+    fun adminEbicsCreateHost() {
+        dbHelper {
+            withTestApplication(sandboxApp) {
+                runBlocking {
+                    val body = mapper.writeValueAsString(
+                        object {
+                            val hostID = "www"
+                            var ebicsVersion = "www"
+                        }
+                    )
+                    // Valid request, good credentials.
+                    var r = client.post<HttpResponse>("/admin/ebics/hosts") {
+                        this.body = body
+                        this.headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                            append(
+                                HttpHeaders.ContentType,
+                                ContentType.Application.Json
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpResponseStatus.OK.code())
+                    r = client.get("/admin/ebics/hosts") {
+                        expectSuccess = false
+
+                    }
+                    assert(r.status.value == 
HttpResponseStatus.UNAUTHORIZED.code())
+                    r = client.get("/admin/ebics/hosts") {
+                        this.headers {
+                            append(
+                                HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == HttpResponseStatus.OK.code())
+                    // Invalid, with good credentials.
+                    r = client.post("/admin/ebics/hosts") {
+                        expectSuccess = false
+                        this.body = "invalid"
+                        this.headers {
+                            append(
+                                io.ktor.http.HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "foo")
+                            )
+                            append(
+                                io.ktor.http.HttpHeaders.ContentType,
+                                ContentType.Application.Json
+                            )
+                        }
+                    }
+                    assert(r.status.value == 
HttpResponseStatus.BAD_REQUEST.code())
+                    // Unauth: admin with wrong password.
+                    r = client.post("/admin/ebics/hosts") {
+                        expectSuccess = false
+                        this.headers {
+                            append(
+                                io.ktor.http.HttpHeaders.Authorization,
+                                buildBasicAuthLine("admin", "bar")
+                            )
+                        }
+                    }
+                    assert(r.status.value == 
HttpResponseStatus.UNAUTHORIZED.code())
+                    // Auth & forbidden resource.
+                    r = client.post("/admin/ebics/hosts") {
+                        expectSuccess = false
+                        this.headers {
+                            append(
+                                io.ktor.http.HttpHeaders.Authorization,
+                                // Exist, but no rights over the EBICS host.
+                                buildBasicAuthLine("foo", "foo")
+                            )
+                        }
+                    }
+                    assert(r.status.value == 
HttpResponseStatus.UNAUTHORIZED.code())
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/SelfContainedDBTest.kt 
b/nexus/src/test/kotlin/SelfContainedDBTest.kt
deleted file mode 100644
index f0322fbc..00000000
--- a/nexus/src/test/kotlin/SelfContainedDBTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-import org.jetbrains.exposed.dao.Entity
-import org.jetbrains.exposed.dao.EntityClass
-import org.jetbrains.exposed.dao.IntEntity
-import org.jetbrains.exposed.dao.IntEntityClass
-import org.jetbrains.exposed.dao.id.EntityID
-import org.jetbrains.exposed.dao.id.IdTable
-import org.jetbrains.exposed.dao.id.IntIdTable
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.SchemaUtils
-import org.jetbrains.exposed.sql.StdOutSqlLogger
-import org.jetbrains.exposed.sql.addLogger
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.junit.Test
-import java.io.File
-
-object ContainedTableWithIntId : IntIdTable() {
-    val column = text("column")
-}
-class ContainedEntityWithIntId(id: EntityID<Int>) : IntEntity(id) {
-    companion object : 
IntEntityClass<ContainedEntityWithIntId>(ContainedTableWithIntId)
-    var column by ContainedTableWithIntId.column
-}
-
-object ContainedTableWithStringId : IdTable<String>() {
-    override val id = varchar("id", 10).entityId()
-    override val primaryKey = PrimaryKey(id, name = "id")
-    val column = text("column")
-
-}
-class ContainedEntityWithStringId(id: EntityID<String>) : Entity<String>(id) {
-    companion object : EntityClass<String, 
ContainedEntityWithStringId>(ContainedTableWithStringId)
-    var column by ContainedTableWithStringId.column
-}
-
-object ContainingTable : IdTable<String>() {
-    override val id = varchar("id", 10).entityId()
-    override val primaryKey = PrimaryKey(id, name = "id")
-    val referenceStringId = reference("referenceStringId", 
ContainedTableWithStringId)
-    val referenceIntId = reference("referenceIntId", ContainedTableWithIntId)
-}
-class ContainingEntity(id: EntityID<String>) : Entity<String>(id) {
-    companion object : EntityClass<String, ContainingEntity>(ContainingTable)
-    var referenceStringId by ContainedEntityWithStringId referencedOn 
ContainingTable.referenceStringId
-    var referenceIntId by ContainedEntityWithIntId referencedOn 
ContainingTable.referenceIntId
-}
-
-class DBTest {
-    @Test
-    fun facadeConfigTest() {
-        withTestDatabase {
-            transaction {
-                addLogger(StdOutSqlLogger)
-                SchemaUtils.create(
-                    ContainingTable,
-                    ContainedTableWithIntId,
-                    ContainedTableWithStringId
-                )
-                val entityWithIntId = ContainedEntityWithIntId.new {
-                    column = "value"
-                }
-                entityWithIntId.flush()
-                val entityWithStringId = 
ContainedEntityWithStringId.new("contained-id") {
-                    column = "another value"
-                }
-                ContainingEntity.new("containing-id") {
-                    referenceIntId = entityWithIntId
-                    referenceStringId = entityWithStringId
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
index e96f14b9..a0617abc 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt
@@ -19,9 +19,6 @@
 
 package tech.libeufin.sandbox
 
-import com.fasterxml.jackson.core.JsonParseException
-import com.fasterxml.jackson.databind.exc.MismatchedInputException
-import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
 import io.ktor.application.*
 import io.ktor.http.HttpStatusCode
 import io.ktor.request.*
@@ -29,6 +26,7 @@ import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
 import org.jetbrains.exposed.sql.and
 import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.util.*
+import java.awt.Label
 import java.math.BigDecimal
 import java.security.interfaces.RSAPublicKey
 import java.util.*
@@ -48,6 +46,23 @@ data class SandboxCamt(
     val creationTime: Long
 )
 
+/**
+ *
+ * Return true if access to the bank account can be granted,
+ * false otherwise.
+ *
+ * Given the policy of having bank account names matching
+ * their owner's username, this function enforces such policy
+ * with the exception that 'admin' can access every bank
+ * account.  A null username indicates disabled authentication
+ * checks, hence it grants the access.
+ */
+fun allowOwnerOrAdmin(username: String?, bankAccountLabel: String): Boolean {
+    if (username == null) return true
+    if (username == "admin") return true
+    return username == bankAccountLabel
+}
+
 /**
  * Throws exception if the credentials are wrong.
  *
@@ -55,14 +70,14 @@ data class SandboxCamt(
  * - null if the authentication is disabled (during tests, for example).
  *   This facilitates tests because allows requests to lack entirely a
  *   Authorization header.
- * - the name of the authenticated user
+ * - the username of the authenticated user
  * - throw exception when the authentication fails
  *
  * Note: at this point it is ONLY checked whether the user provided
  * a valid password for the username mentioned in the Authorization header.
  * The actual access to the resources must be later checked by each handler.
  */
-fun ApplicationRequest.basicAuth(): String? {
+fun ApplicationRequest.basicAuth(onlyAdmin: Boolean = false): String? {
     val withAuth = this.call.ensureAttribute(WITH_AUTH_ATTRIBUTE_KEY)
     if (!withAuth) {
         logger.info("Authentication is disabled - assuming tests currently 
running.")
@@ -77,6 +92,10 @@ fun ApplicationRequest.basicAuth(): String? {
         )
         return credentials.first
     }
+    /**
+     * If only admin auth was allowed, here it failed already,
+     * hence throw 401.  */
+    if (onlyAdmin) throw unauthorized("Only admin allowed.")
     val passwordHash = transaction {
         val customer = getCustomer(credentials.first)
         customer.passwordHash
@@ -131,7 +150,15 @@ fun getHistoryElementFromTransactionRow(
     return getHistoryElementFromTransactionRow(dbRow.transactionRef)
 }
 
-// Need to be called within a transaction {} block.
+/**
+ * Need to be called within a transaction {} block.  It
+ * is acceptable to pass a bank account's label as the
+ * parameter, because usernames can only own one bank
+ * account whose label equals the owner's username.
+ *
+ * Future versions may relax this policy to allow one
+ * customer to own multiple bank accounts.
+ */
 fun getCustomer(username: String): DemobankCustomerEntity {
     return DemobankCustomerEntity.find {
         DemobankCustomersTable.username eq username
@@ -378,7 +405,7 @@ fun getEbicsSubscriberFromDetails(userID: String, 
partnerID: String, hostID: Str
                     (EbicsSubscribersTable.hostId eq hostID)
         }.firstOrNull() ?: throw SandboxError(
             HttpStatusCode.NotFound,
-            "Ebics subscriber not found"
+            "Ebics subscriber (${userID}, ${partnerID}, ${hostID}) not found"
         )
     }
 }
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
index 0772919a..3ed0eba4 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt
@@ -82,12 +82,22 @@ data class EbicsSubscriberObsoleteApi(
     val userID: String,
     val systemID: String? = null
 )
+
+/**
+ * Allows the admin to associate a new bank account
+ * to a EBICS subscriber.
+ */
 data class EbicsBankAccountRequest(
     val subscriber: EbicsSubscriberObsoleteApi,
     val iban: String,
     val bic: String,
     val name: String,
     val label: String,
+    /**
+     * Customer username that will own this
+     * EBICS subscriber.
+     */
+    val owner: String
 )
 
 data class CustomerRegistration(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index b5592350..91e7c312 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -83,7 +83,6 @@ import javax.xml.bind.JAXBContext
 import kotlin.system.exitProcess
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-private val currencyEnv: String? = System.getenv("LIBEUFIN_SANDBOX_CURRENCY")
 const val SANDBOX_DB_ENV_VAR_NAME = "LIBEUFIN_SANDBOX_DB_CONNECTION"
 private val adminPassword: String? = 
System.getenv("LIBEUFIN_SANDBOX_ADMIN_PASSWORD")
 var WITH_AUTH = true // Needed by helpers too, hence not making it private.
@@ -588,16 +587,18 @@ val sandboxApp: Application.() -> Unit = {
                 ContentType.Text.Plain
             )
         }
-
         // Respond with the last statement of the requesting account.
         // Query details in the body.
         post("/admin/payments/camt") {
-            call.request.basicAuth()
+            val username = call.request.basicAuth()
             val body = call.receiveJson<CamtParams>()
             if (body.type != 53) throw SandboxError(
                 HttpStatusCode.NotFound,
                 "Only Camt.053 documents can be generated."
             )
+            if (!allowOwnerOrAdmin(username, body.bankaccount))
+                throw unauthorized("User '${username}' has no rights over" +
+                        " bank account '${body.bankaccount}'")
             val camtMessage = transaction {
                 val bankaccount = getBankAccountFromLabel(
                     body.bankaccount,
@@ -616,16 +617,34 @@ val sandboxApp: Application.() -> Unit = {
             return@post
         }
 
-        // create a new bank account, no EBICS relation.
+        /**
+         * Create a new bank account, no EBICS relation.  Okay
+         * to let a user, since having a particular username allocates
+         * already a bank account with such label.
+         */
         post("/admin/bank-accounts/{label}") {
             val username = call.request.basicAuth()
             val body = call.receiveJson<BankAccountInfo>()
+            if (!allowOwnerOrAdmin(username, body.label))
+                throw unauthorized("User '$username' has no rights over" +
+                        " bank account '${body.label}'"
+                )
             transaction {
+                val maybeBankAccount = BankAccountEntity.find {
+                    BankAccountsTable.label eq body.label
+                }.firstOrNull()
+                if (maybeBankAccount != null)
+                    throw conflict("Bank account '${body.label}' exist 
already")
                 BankAccountEntity.new {
                     iban = body.iban
                     bic = body.bic
                     label = body.label
-                    owner = username ?: "admin" // allows
+                    /**
+                     * The null username case exist when auth is
+                     * disabled.  In this case, we assign the bank
+                     * account to 'admin'.
+                     */
+                    owner = username ?: "admin"
                     demoBank = getDefaultDemobank()
                 }
             }
@@ -635,11 +654,13 @@ val sandboxApp: Application.() -> Unit = {
 
         // Information about one bank account.
         get("/admin/bank-accounts/{label}") {
-            call.request.basicAuth()
+            val username = call.request.basicAuth()
             val label = call.getUriComponent("label")
             val ret = transaction {
                 val demobank = getDefaultDemobank()
                 val bankAccount = getBankAccountFromLabel(label, demobank)
+                if (!allowOwnerOrAdmin(username, label))
+                    throw unauthorized("'${username}' has no rights over 
'$label'")
                 val balance = balanceForAccount(bankAccount)
                 object {
                     val balance = "${bankAccount.demoBank.currency}:${balance}"
@@ -655,9 +676,8 @@ val sandboxApp: Application.() -> Unit = {
         // Book one incoming payment for the requesting account.
         // The debtor is not required to have an account at this Sandbox.
         post("/admin/bank-accounts/{label}/simulate-incoming-transaction") {
-            val username = call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val body = call.receiveJson<IncomingPaymentInfo>()
-            // FIXME: generate nicer UUID!
             val accountLabel = ensureNonNull(call.parameters["label"])
             val reqDebtorBic = body.debtorBic
             if (reqDebtorBic != null && !validateBic(reqDebtorBic)) {
@@ -680,10 +700,11 @@ val sandboxApp: Application.() -> Unit = {
                     accountLabel, demobank
                 )
                 val randId = getRandomString(16)
+                val customer = getCustomer(accountLabel)
                 BankAccountTransactionEntity.new {
                     creditorIban = account.iban
                     creditorBic = account.bic
-                    creditorName = getPersonNameFromCustomer(username)
+                    creditorName = customer.name ?: "Name not given."
                     debtorIban = body.debtorIban
                     debtorBic = reqDebtorBic
                     debtorName = body.debtorName
@@ -701,8 +722,13 @@ val sandboxApp: Application.() -> Unit = {
         }
         // Associates a new bank account with an existing Ebics subscriber.
         post("/admin/ebics/bank-accounts") {
-            val username = call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val body = call.receiveJson<EbicsBankAccountRequest>()
+            if (body.owner != body.label)
+                throw conflict(
+                    "Customer username '${body.owner}'" +
+                            " differs from bank account name '${body.label}'"
+                )
             if (!validateBic(body.bic)) {
                 throw SandboxError(HttpStatusCode.BadRequest, "invalid BIC 
(${body.bic})")
             }
@@ -712,8 +738,9 @@ val sandboxApp: Application.() -> Unit = {
                     body.subscriber.partnerID,
                     body.subscriber.hostID
                 )
+                if (subscriber.bankAccount != null)
+                    throw conflict("subscriber has already a bank account: 
${subscriber.bankAccount?.label}")
                 val demobank = getDefaultDemobank()
-
                 /**
                  * Checking that the default demobank doesn't have already the
                  * requested IBAN and bank account label.
@@ -733,7 +760,7 @@ val sandboxApp: Application.() -> Unit = {
                     iban = body.iban
                     bic = body.bic
                     label = body.label
-                    owner = username ?: "admin"
+                    owner = body.owner
                     demoBank = demobank
                 }
             }
@@ -743,7 +770,7 @@ val sandboxApp: Application.() -> Unit = {
 
         // Information about all the default demobank's bank accounts
         get("/admin/bank-accounts") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val accounts = mutableListOf<BankAccountInfo>()
             transaction {
                 val demobank = getDefaultDemobank()
@@ -765,49 +792,52 @@ val sandboxApp: Application.() -> Unit = {
 
         // Details of all the transactions of one bank account.
         get("/admin/bank-accounts/{label}/transactions") {
-            call.request.basicAuth()
+            val username = call.request.basicAuth()
             val ret = AccountTransactions()
+            val accountLabel = ensureNonNull(call.parameters["label"])
+            if (!allowOwnerOrAdmin(username, accountLabel))
+                throw unauthorized("Requesting user '${username}'" +
+                        " has no rights over bank account '${accountLabel}'"
+            )
             transaction {
-                val accountLabel = ensureNonNull(call.parameters["label"])
-                transaction {
-                    val demobank = getDefaultDemobank()
-                    val account = getBankAccountFromLabel(accountLabel, 
demobank)
-                    BankAccountTransactionEntity.find {
-                        BankAccountTransactionsTable.account eq account.id
-                    }.forEach {
-                        ret.payments.add(
-                            PaymentInfo(
-                                accountLabel = account.label,
-                                creditorIban = it.creditorIban,
-                                accountServicerReference = 
it.accountServicerReference,
-                                paymentInformationId = it.pmtInfId,
-                                debtorIban = it.debtorIban,
-                                subject = it.subject,
-                                date = GMTDate(it.date).toHttpDate(),
-                                amount = it.amount,
-                                creditorBic = it.creditorBic,
-                                creditorName = it.creditorName,
-                                debtorBic = it.debtorBic,
-                                debtorName = it.debtorName,
-                                currency = it.currency,
-                                creditDebitIndicator = when (it.direction) {
-                                    "CRDT" -> "credit"
-                                    "DBIT" -> "debit"
-                                    else -> throw Error("invalid direction")
-                                }
-                            )
+                val demobank = getDefaultDemobank()
+                val account = getBankAccountFromLabel(accountLabel, demobank)
+                BankAccountTransactionEntity.find {
+                    BankAccountTransactionsTable.account eq account.id
+                }.forEach {
+                    ret.payments.add(
+                        PaymentInfo(
+                            accountLabel = account.label,
+                            creditorIban = it.creditorIban,
+                            accountServicerReference = 
it.accountServicerReference,
+                            paymentInformationId = it.pmtInfId,
+                            debtorIban = it.debtorIban,
+                            subject = it.subject,
+                            date = GMTDate(it.date).toHttpDate(),
+                            amount = it.amount,
+                            creditorBic = it.creditorBic,
+                            creditorName = it.creditorName,
+                            debtorBic = it.debtorBic,
+                            debtorName = it.debtorName,
+                            currency = it.currency,
+                            creditDebitIndicator = when (it.direction) {
+                                "CRDT" -> "credit"
+                                "DBIT" -> "debit"
+                                else -> throw Error("invalid direction")
+                            }
                         )
-                    }
+                    )
                 }
             }
             call.respond(ret)
         }
-
-        // Generate one incoming and one outgoing transactions for
-        // one bank account.  Counterparts do not need to have an account
-        // at this Sandbox.
+        /**
+         * Generate one incoming and one outgoing transactions for
+         * one bank account.  Counterparts do not need to have an account
+         * at this Sandbox.
+         */
         post("/admin/bank-accounts/{label}/generate-transactions") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             transaction {
                 val accountLabel = ensureNonNull(call.parameters["label"])
                 val demobank = getDefaultDemobank()
@@ -865,9 +895,18 @@ val sandboxApp: Application.() -> Unit = {
          * user is allowed to call this.
          */
         post("/admin/ebics/subscribers") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val body = call.receiveJson<EbicsSubscriberObsoleteApi>()
             transaction {
+                // Check it exists first.
+                val maybeSubscriber = EbicsSubscriberEntity.find {
+                    EbicsSubscribersTable.userId eq body.userID and (
+                            EbicsSubscribersTable.partnerId eq body.partnerID
+                            ) and (
+                            EbicsSubscribersTable.systemId eq body.systemID
+                                    )
+                }.firstOrNull()
+                if (maybeSubscriber != null) throw conflict("EBICS subscriber 
exists already")
                 EbicsSubscriberEntity.new {
                     partnerId = body.partnerID
                     userId = body.userID
@@ -886,7 +925,7 @@ val sandboxApp: Application.() -> Unit = {
 
         // Shows details of all the EBICS subscribers of this Sandbox.
         get("/admin/ebics/subscribers") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val ret = AdminGetSubscribers()
             transaction {
                 EbicsSubscriberEntity.all().forEach {
@@ -906,7 +945,7 @@ val sandboxApp: Application.() -> Unit = {
 
         // Change keys used in the EBICS communications.
         post("/admin/ebics/hosts/{hostID}/rotate-keys") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val hostID: String = call.parameters["hostID"] ?: throw 
SandboxError(
                 io.ktor.http.HttpStatusCode.BadRequest, "host ID missing in 
URL"
             )
@@ -933,7 +972,7 @@ val sandboxApp: Application.() -> Unit = {
 
         // Create a new EBICS host
         post("/admin/ebics/hosts") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val req = call.receiveJson<EbicsHostCreateRequest>()
             val pairA = CryptoUtil.generateRsaKeyPair(2048)
             val pairB = CryptoUtil.generateRsaKeyPair(2048)
@@ -957,7 +996,7 @@ val sandboxApp: Application.() -> Unit = {
 
         // Show the names of all the Ebics hosts
         get("/admin/ebics/hosts") {
-            call.request.basicAuth()
+            call.request.basicAuth(onlyAdmin = true)
             val ebicsHosts = transaction {
                 EbicsHostEntity.all().map { it.hostId }
             }

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