gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Implementing PATCH /auth


From: gnunet
Subject: [libeufin] branch master updated: Implementing PATCH /auth
Date: Tue, 03 Oct 2023 12:45:19 +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 4b4fba43 Implementing PATCH /auth
4b4fba43 is described below

commit 4b4fba43ed644a39ceb5a19b3eb539e5f1d1bb55
Author: MS <ms@taler.net>
AuthorDate: Tue Oct 3 12:45:11 2023 +0200

    Implementing PATCH /auth
---
 .../main/kotlin/tech/libeufin/bank/BankMessages.kt |  11 ++-
 .../tech/libeufin/bank/CorebankApiHandlers.kt      | 109 ++++++++++++---------
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |   1 +
 bank/src/test/kotlin/DatabaseTest.kt               |  11 +++
 bank/src/test/kotlin/LibeuFinApiTest.kt            |  34 +++++++
 5 files changed, 116 insertions(+), 50 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index 01f4da66..15188d41 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -657,9 +657,18 @@ data class TransferResponse(
 data class PublicAccountsResponse(
     val public_accounts: MutableList<PublicAccount> = mutableListOf()
 )
+
+/**
+ * Single element of GET /public-accounts list.
+ */
 @Serializable
 data class PublicAccount(
     val payto_uri: String,
     val balance: Balance,
     val account_name: String
-)
\ No newline at end of file
+)
+
+@Serializable
+data class AccountPasswordChange(
+    val new_password: String
+)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index 130609bf..a6a4f66f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -24,6 +24,7 @@ private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.account
  */
 fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
 
+    // TOKEN ENDPOINTS
     delete("/accounts/{USERNAME}/token") {
         val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
         /**
@@ -52,7 +53,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
          */
         call.respond(HttpStatusCode.NoContent)
     }
-
     post("/accounts/{USERNAME}/token") {
         val customer =
             call.authenticateBankRequest(db, TokenScope.refreshable) ?: throw 
unauthorized("Authentication failed")
@@ -116,7 +116,46 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         )
         return@post
     }
-
+    // ACCOUNT ENDPOINTS
+    get("/public-accounts") {
+        // no authentication here.
+        val publicAccounts = db.accountsGetPublic(ctx.currency)
+        if (publicAccounts.isEmpty()) {
+            call.respond(HttpStatusCode.NoContent)
+            return@get
+        }
+        call.respond(
+            PublicAccountsResponse().apply {
+                publicAccounts.forEach {
+                    this.public_accounts.add(it)
+                }
+            }
+        )
+        return@get
+    }
+    get("/accounts") {
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
+        if (c.login != "admin") throw forbidden("Only admin allowed.")
+        // Get optional param.
+        val maybeFilter: String? = call.request.queryParameters["filter_name"]
+        logger.debug("Filtering on '${maybeFilter}'")
+        val queryParam = if (maybeFilter != null) {
+            "%${maybeFilter}%"
+        } else "%"
+        val dbRes = db.accountsGetForAdmin(queryParam)
+        if (dbRes.isEmpty()) {
+            call.respond(HttpStatusCode.NoContent)
+            return@get
+        }
+        call.respond(
+            ListBankAccountsResponse().apply {
+                dbRes.forEach { element ->
+                    this.accounts.add(element)
+                }
+            }
+        )
+        return@get
+    }
     post("/accounts") { // check if only admin is allowed to create new 
accounts
         if (ctx.restrictRegistration) {
             val customer: Customer? = call.authenticateBankRequest(db, 
TokenScope.readwrite)
@@ -220,46 +259,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         call.respond(HttpStatusCode.Created)
         return@post
     }
-    get("/public-accounts") {
-        // no authentication here.
-        val publicAccounts = db.accountsGetPublic(ctx.currency)
-        if (publicAccounts.isEmpty()) {
-            call.respond(HttpStatusCode.NoContent)
-            return@get
-        }
-        call.respond(
-            PublicAccountsResponse().apply {
-                publicAccounts.forEach {
-                    this.public_accounts.add(it)
-                }
-            }
-        )
-        return@get
-    }
-    get("/accounts") {
-        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
-        if (c.login != "admin") throw forbidden("Only admin allowed.")
-        // Get optional param.
-        val maybeFilter: String? = call.request.queryParameters["filter_name"]
-        logger.debug("Filtering on '${maybeFilter}'")
-        val queryParam = if (maybeFilter != null) {
-            "%${maybeFilter}%"
-        } else "%"
-        val dbRes = db.accountsGetForAdmin(queryParam)
-        if (dbRes.isEmpty()) {
-            call.respond(HttpStatusCode.NoContent)
-            return@get
-        }
-        call.respond(
-            ListBankAccountsResponse().apply {
-                dbRes.forEach { element ->
-                    this.accounts.add(element)
-                }
-            }
-        )
-        return@get
-    }
-
     get("/accounts/{USERNAME}") {
         val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized("Login failed")
         val resourceName = call.maybeUriComponent("USERNAME") ?: throw 
badRequest(
@@ -324,7 +323,24 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         }
         return@delete
     }
-
+    patch("/accounts/{USERNAME}/auth") {
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
+        val accountName = call.getResourceName("USERNAME")
+        if (!accountName.canI(c, withAdmin = true)) throw forbidden()
+        val req = call.receive<AccountPasswordChange>()
+        val hashedPassword = CryptoUtil.hashpw(req.new_password)
+        if (!db.customerChangePassword(
+                accountName,
+                hashedPassword
+        ))
+            throw notFound(
+                "Account '$accountName' not found",
+                talerEc = TalerErrorCode.TALER_EC_END // FIXME: need at least 
GENERIC_NOT_FOUND.
+            )
+        call.respond(HttpStatusCode.NoContent)
+        return@patch
+    }
+    // WITHDRAWAL ENDPOINTS
     post("/accounts/{USERNAME}/withdrawals") {
         val c = call.authenticateBankRequest(db, TokenScope.readwrite)
             ?: throw unauthorized() // Admin not allowed to withdraw in the 
name of customers:
@@ -356,7 +372,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         )
         return@post
     }
-
     get("/withdrawals/{withdrawal_id}") {
         val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
         call.respond(
@@ -371,7 +386,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         )
         return@get
     }
-
     post("/withdrawals/{withdrawal_id}/abort") {
         val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id")) 
// Idempotency:
         if (op.aborted) {
@@ -384,7 +398,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         call.respondText("{}", ContentType.Application.Json)
         return@post
     }
-
     post("/withdrawals/{withdrawal_id}/confirm") {
         val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id")) 
// Checking idempotency:
         if (op.confirmationDone) {
@@ -434,7 +447,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         }
         return@post
     }
-
+    // TRANSACTION ENDPOINT
     get("/accounts/{USERNAME}/transactions") {
         val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
         val resourceName = call.expectUriComponent("USERNAME")
@@ -465,7 +478,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         call.respond(res)
         return@get
     }
-
     // Creates a bank transaction.
     post("/accounts/{USERNAME}/transactions") {
         val c: Customer = call.authenticateBankRequest(db, 
TokenScope.readwrite) ?: throw unauthorized()
@@ -512,7 +524,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: 
BankApplicationContext) {
         }
         return@post
     }
-
     get("/accounts/{USERNAME}/transactions/{T_ID}") {
         val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
         val accountOwner = call.expectUriComponent("USERNAME") // auth ok, 
check rights.
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 2db84c3c..7d329399 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -578,6 +578,7 @@ class ChangePw : CliktCommand("Change account password", 
name = "passwd") {
 
         if (!db.customerChangePassword(account, CryptoUtil.hashpw(password))) {
             println("password change failed")
+            exitProcess(1)
         } else {
             println("password change succeeded")
         }
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 17f1882b..5a1acb3e 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -425,6 +425,17 @@ class DatabaseTest {
         assert(db.accountsGetForAdmin("%ar").size == 1) // gets Bar only
     }
 
+    @Test
+    fun passwordChangeTest() {
+        val db = initDb()
+        // foo not found, this fails.
+        assert(!db.customerChangePassword("foo", "won't make it"))
+        // creating foo.
+        assert(db.customerCreate(customerFoo) != null)
+        // foo exists, this succeeds.
+        assert(db.customerChangePassword("foo", CryptoUtil.hashpw("new-pw")))
+    }
+
     @Test
     fun getPublicAccountsTest() {
         val db = initDb()
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 05deba30..9984a72c 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -181,6 +181,40 @@ class LibeuFinApiTest {
         }
     }
 
+    @Test
+    fun passwordChangeTest() {
+        val db = initDb()
+        val ctx = getTestContext()
+        assert(db.customerCreate(customerFoo) != null)
+        testApplication {
+            application {
+                corebankWebApp(db, ctx)
+            }
+            // Changing the password.
+            client.patch("/accounts/foo/auth") {
+                expectSuccess = true
+                contentType(ContentType.Application.Json)
+                basicAuth("foo", "pw")
+                setBody("""{"new_password": "bar"}""")
+            }
+            // Previous password should fail.
+            client.patch("/accounts/foo/auth") {
+                expectSuccess = false
+                contentType(ContentType.Application.Json)
+                basicAuth("foo", "pw")
+                setBody("""{"new_password": "not-even-parsed"}""")
+            }.apply {
+                assert(this.status == HttpStatusCode.Unauthorized)
+            }
+            // New password should succeed.
+            client.patch("/accounts/foo/auth") {
+                expectSuccess = true
+                contentType(ContentType.Application.Json)
+                basicAuth("foo", "bar")
+                setBody("""{"new_password": "not-used"}""")
+            }
+        }
+    }
     @Test
     fun tokenDeletionTest() {
         val db = initDb()

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