gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: implementing token deletion


From: gnunet
Subject: [libeufin] branch master updated: implementing token deletion
Date: Tue, 03 Oct 2023 09:44:41 +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 c58a867f implementing token deletion
c58a867f is described below

commit c58a867f3e0a32f5bfa7cfa1e40ad02cbfa1907d
Author: MS <ms@taler.net>
AuthorDate: Tue Oct 3 09:44:23 2023 +0200

    implementing token deletion
---
 .../tech/libeufin/bank/CorebankApiHandlers.kt      | 27 +++++++++++-
 .../src/main/kotlin/tech/libeufin/bank/Database.kt | 26 +++++++++--
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt |  3 +-
 bank/src/test/kotlin/DatabaseTest.kt               | 29 ++++++++++++-
 bank/src/test/kotlin/LibeuFinApiTest.kt            | 50 ++++++++++++++++++++++
 5 files changed, 128 insertions(+), 7 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index 489b71e0..6770e559 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -25,7 +25,32 @@ private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.account
 fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
 
     delete("/accounts/{USERNAME}/token") {
-        throw internalServerError("Token deletion not implemented.")
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
+        /**
+         * The following command ensures that this call was
+         * authenticated with the bearer token and NOT with
+         * basic auth. FIXME: this "409 Conflict" case is not documented.
+         */
+        val token = call.getAuthToken() ?: throw badRequest("Basic auth not 
supported here.")
+        val resourceName = call.getResourceName("USERNAME")
+        /**
+         * The following check makes sure that the token belongs
+         * to the username contained in {USERNAME}.
+         */
+        if (!resourceName.canI(c, withAdmin = true)) throw forbidden()
+
+        /**
+         * Not sanity-checking the token, as it was used by the authentication 
already.
+         * If harder errors happen, then they'll get Ktor respond with 500.
+         */
+        db.bearerTokenDelete(Base32Crockford.decode(token))
+        /**
+         * Responding 204 regardless of it being actually deleted or not.
+         * If it wasn't found, then it must have been deleted before we
+         * reached here, but the token was valid as it served the 
authentication
+         * => no reason to fail the request.
+         */
+        call.respond(HttpStatusCode.NoContent)
     }
 
     post("/accounts/{USERNAME}/token") {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index a18f5bd2..e221bd2c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -243,10 +243,10 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
               FROM customer_delete(?);
         """)
         stmt.setString(1, login)
-        stmt.executeQuery().apply {
-            if (!this.next()) throw internalServerError("Deletion returned 
nothing.")
-            if (this.getBoolean("out_nx_customer")) return 
CustomerDeletionResult.CUSTOMER_NOT_FOUND
-            if (this.getBoolean("out_balance_not_zero")) return 
CustomerDeletionResult.BALANCE_NOT_ZERO
+        stmt.executeQuery().use {
+            if (!it.next()) throw internalServerError("Deletion returned 
nothing.")
+            if (it.getBoolean("out_nx_customer")) return 
CustomerDeletionResult.CUSTOMER_NOT_FOUND
+            if (it.getBoolean("out_balance_not_zero")) return 
CustomerDeletionResult.BALANCE_NOT_ZERO
             return CustomerDeletionResult.SUCCESS
         }
     }
@@ -378,6 +378,24 @@ class Database(private val dbConfig: String, private val 
bankCurrency: String) {
             )
         }
     }
+    /**
+     * Deletes a bearer token from the database.  Returns true,
+     * if deletion succeeds or false if the token could not be
+     * deleted (= not found).
+     */
+    fun bearerTokenDelete(token: ByteArray): Boolean {
+        reconnect()
+        val stmt = prepare("""
+            DELETE FROM bearer_tokens
+              WHERE content = ?
+              RETURNING bearer_token_id;
+        """)
+        stmt.setBytes(1, token)
+        stmt.executeQuery().use {
+            if (!it.next()) return false;
+            return true
+        }
+    }
 
     // MIXED CUSTOMER AND BANK ACCOUNT DATA
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 9f08f447..5329efa1 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -105,7 +105,8 @@ fun doTokenAuth(
     requiredScope: TokenScope,
 ): Customer? {
     val bareToken = splitBearerToken(token) ?: throw badRequest(
-        "Bearer token malformed", talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+        "Bearer token malformed",
+        talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
     )
     val tokenBytes = try {
         Base32Crockford.decode(bareToken)
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 89415b3a..55d4a1c9 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -25,6 +25,7 @@ import java.sql.DriverManager
 import java.time.Instant
 import java.util.Random
 import java.util.UUID
+import kotlin.experimental.inv
 
 // Foo pays Bar with custom subject.
 fun genTx(
@@ -143,10 +144,36 @@ class DatabaseTest {
             scope = TokenScope.readonly
         )
         assert(db.bearerTokenGet(token.content) == null)
-        db.customerCreate(customerBar) // Tokens need owners.
+        assert(db.customerCreate(customerBar) != null) // Tokens need owners.
         assert(db.bearerTokenCreate(token))
         assert(db.bearerTokenGet(tokenBytes) != null)
     }
+
+    @Test
+    fun tokenDeletionTest() {
+        val db = initDb()
+        val token = ByteArray(32)
+        // Token not there, must fail.
+        assert(!db.bearerTokenDelete(token))
+        assert(db.customerCreate(customerBar) != null) // Tokens need owners.
+        assert(db.bearerTokenCreate(
+            BearerToken(
+                bankCustomer = 1L,
+                content = token,
+                creationTime = Instant.now(),
+                expirationTime = Instant.now().plusSeconds(10),
+                scope = TokenScope.readwrite
+            )
+        ))
+        // Wrong token given, must fail
+        val anotherToken = token.map {
+            it.inv() // flipping every bit.
+        }
+        assert(!db.bearerTokenDelete(anotherToken.toByteArray()))
+        // Token there, must succeed.
+        assert(db.bearerTokenDelete(token))
+    }
+
     @Test
     fun bankTransactionsTest() {
         val db = initDb()
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 3b63183a..a66bd062 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -180,6 +180,56 @@ class LibeuFinApiTest {
         }
     }
 
+    @Test
+    fun tokenDeletionTest() {
+        val db = initDb()
+        val ctx = getTestContext()
+        assert(db.customerCreate(customerFoo) != null)
+        val token = ByteArray(32)
+        Random.nextBytes(token)
+        assert(db.bearerTokenCreate(
+            BearerToken(
+                bankCustomer = 1L,
+                content = token,
+                creationTime = Instant.now(),
+                expirationTime = Instant.now().plusSeconds(10),
+                scope = TokenScope.readwrite
+            )
+        ))
+        testApplication {
+            application {
+                corebankWebApp(db, ctx)
+            }
+            // Legitimate first attempt, should succeed
+            client.delete("/accounts/foo/token") {
+                expectSuccess = true
+                headers["Authorization"] = "Bearer 
secret-token:${Base32Crockford.encode(token)}"
+            }.apply {
+                assert(this.status == HttpStatusCode.NoContent)
+            }
+            // Trying after deletion should hit 404.
+            client.delete("/accounts/foo/token") {
+                expectSuccess = false
+                headers["Authorization"] = "Bearer 
secret-token:${Base32Crockford.encode(token)}"
+            }.apply {
+                assert(this.status == HttpStatusCode.Unauthorized)
+            }
+            // Checking foo can still be served by basic auth, after token 
deletion.
+            assert(db.bankAccountCreate(
+                BankAccount(
+                    hasDebt = false,
+                    internalPaytoUri = "payto://iban/DE1234",
+                    maxDebt = TalerAmount(100, 0, "KUDOS"),
+                    owningCustomerId = 1
+                )
+            ) != null)
+            client.get("/accounts/foo") {
+                expectSuccess = true
+                basicAuth("foo", "pw")
+            }
+        }
+    }
+
     // Creating token with "forever" duration.
     @Test
     fun tokenForeverTest() {

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