gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Implment token-based authentication.


From: gnunet
Subject: [libeufin] branch master updated: Implment token-based authentication.
Date: Tue, 12 Oct 2021 10:25:06 +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 48d69b4  Implment token-based authentication.
48d69b4 is described below

commit 48d69b46a9a7d83af995e6e73f51f1ad86dd33d9
Author: ms <ms@taler.net>
AuthorDate: Tue Oct 12 10:24:57 2021 +0200

    Implment token-based authentication.
---
 .../src/main/kotlin/tech/libeufin/sandbox/Auth.kt  |  6 +-
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  |  1 -
 .../src/main/kotlin/tech/libeufin/sandbox/Main.kt  | 67 ++++++----------------
 util/src/main/kotlin/HTTP.kt                       | 47 +++++++++++++++
 util/src/test/kotlin/AuthTokenTest.kt              | 34 +++++++++++
 5 files changed, 101 insertions(+), 54 deletions(-)

diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
index 8368dc7..2ec766e 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
@@ -1,15 +1,17 @@
 package tech.libeufin.sandbox
 
 import UtilError
+import io.ktor.application.*
 import io.ktor.http.*
 import io.ktor.request.*
-import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
+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.
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index ec4d00f..0659c73 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -502,7 +502,6 @@ fun getLastBalance(bankAccount: BankAccountEntity): 
BigDecimal {
 private fun constructCamtResponse(
     type: Int,
     subscriber: EbicsSubscriberEntity,
-    // fixes #6243
     dateRange: Pair<Long, Long>?): List<String> {
 
     if (type != 53 && type != 52) throw EbicsUnsupportedOrderType()
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index d0e8f7e..1fe92c6 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -97,6 +97,8 @@ private val baseUrl = URL(
     getValueFromEnv("LIBEUFIN_SANDBOX_BASE_URL") ?: throw Exception(
         "env LIBEUFIN_SANDBOX_BASE_URL is not defined")
 )
+// when null, privileged operations turn impossible
+private val sandboxToken: String? = getValueFromEnv("LIBEUFIN_SANDBOX_TOKEN")
 
 data class SandboxError(
     val statusCode: HttpStatusCode,
@@ -107,42 +109,6 @@ data class SandboxError(
 data class SandboxErrorJson(val error: SandboxErrorDetailJson)
 data class SandboxErrorDetailJson(val type: String, val description: String)
 
-class Superuser : CliktCommand("Add superuser or change pw") {
-    private val username by argument()
-    private val password by option().prompt(requireConfirmation = true, 
hideInput = true)
-    override fun run() {
-        execThrowableOrTerminate {
-            dbCreateTables(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME))
-        }
-        try {
-            requireValidResourceName(username)
-        } catch (e: UtilError) {
-            println(e) // Gives instructions about the allowed format.
-            exitProcess(1)
-        }
-        transaction {
-            val user = SandboxUserEntity.find {
-                SandboxUsersTable.username eq username
-            }.firstOrNull()
-
-            val hashedPw = CryptoUtil.hashpw(password)
-            if (user == null) {
-                SandboxUserEntity.new {
-                    this.username = this@Superuser.username
-                    this.passwordHash = hashedPw
-                    this.superuser = true
-                }
-            } else {
-                if (!user.superuser) {
-                    println("Can only change password for superuser with this 
command.")
-                    throw ProgramResult(1)
-                }
-                user.passwordHash = hashedPw
-            }
-        }
-    }
-}
-
 class Config : CliktCommand("Insert one configuration into the database") {
     init {
         context {
@@ -371,7 +337,6 @@ class SandboxCommand : CliktCommand(invokeWithoutSubcommand 
= true, printHelpOnE
 
 fun main(args: Array<String>) {
     SandboxCommand().subcommands(
-        Superuser(),
         Serve(),
         ResetTables(),
         Config(),
@@ -513,7 +478,7 @@ val sandboxApp: Application.() -> Unit = {
          * requesting account.
          */
         post("/admin/payments/camt") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val body = call.receiveJson<CamtParams>()
             val bankaccount = getAccountFromLabel(body.bankaccount)
             if (body.type != 53) throw SandboxError(
@@ -535,7 +500,7 @@ val sandboxApp: Application.() -> Unit = {
         }
 
         post("/admin/bank-accounts/{label}") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val body = call.receiveJson<BankAccountInfo>()
             transaction {
                 tech.libeufin.sandbox.BankAccountEntity.new {
@@ -551,7 +516,7 @@ val sandboxApp: Application.() -> Unit = {
         }
 
         get("/admin/bank-accounts/{label}") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val label = ensureNonNull(call.parameters["label"])
             val ret = transaction {
                 val bankAccount = tech.libeufin.sandbox.BankAccountEntity.find 
{
@@ -574,7 +539,7 @@ val sandboxApp: Application.() -> Unit = {
         }
 
         post("/admin/bank-accounts/{label}/simulate-incoming-transaction") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val body = call.receiveJson<IncomingPaymentInfo>()
             // FIXME: generate nicer UUID!
             val accountLabel = ensureNonNull(call.parameters["label"])
@@ -617,7 +582,7 @@ val sandboxApp: Application.() -> Unit = {
          * Associates a new bank account with an existing Ebics subscriber.
          */
         post("/admin/ebics/bank-accounts") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val body = call.receiveJson<BankAccountRequest>()
             if (!validateBic(body.bic)) {
                 throw SandboxError(io.ktor.http.HttpStatusCode.BadRequest, 
"invalid BIC (${body.bic})")
@@ -647,7 +612,7 @@ val sandboxApp: Application.() -> Unit = {
             return@post
         }
         get("/admin/bank-accounts") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val accounts = mutableListOf<BankAccountInfo>()
             transaction {
                 tech.libeufin.sandbox.BankAccountEntity.all().forEach {
@@ -665,7 +630,7 @@ val sandboxApp: Application.() -> Unit = {
             call.respond(accounts)
         }
         get("/admin/bank-accounts/{label}/transactions") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val ret = AccountTransactions()
             transaction {
                 val accountLabel = ensureNonNull(call.parameters["label"])
@@ -704,7 +669,7 @@ val sandboxApp: Application.() -> Unit = {
             call.respond(ret)
         }
         post("/admin/bank-accounts/{label}/generate-transactions") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             transaction {
                 val accountLabel = ensureNonNull(call.parameters["label"])
                 val account = getBankAccountFromLabel(accountLabel)
@@ -756,7 +721,7 @@ val sandboxApp: Application.() -> Unit = {
          * Creates a new Ebics subscriber.
          */
         post("/admin/ebics/subscribers") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val body = call.receiveJson<EbicsSubscriberElement>()
             transaction {
                 tech.libeufin.sandbox.EbicsSubscriberEntity.new {
@@ -778,7 +743,7 @@ val sandboxApp: Application.() -> Unit = {
          * Shows all the Ebics subscribers' details.
          */
         get("/admin/ebics/subscribers") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val ret = AdminGetSubscribers()
             transaction {
                 tech.libeufin.sandbox.EbicsSubscriberEntity.all().forEach {
@@ -795,7 +760,7 @@ val sandboxApp: Application.() -> Unit = {
             return@get
         }
         post("/admin/ebics/hosts/{hostID}/rotate-keys") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val hostID: String = call.parameters["hostID"] ?: throw 
SandboxError(
                 io.ktor.http.HttpStatusCode.BadRequest, "host ID missing in 
URL"
             )
@@ -824,7 +789,7 @@ val sandboxApp: Application.() -> Unit = {
          * Creates a new EBICS host.
          */
         post("/admin/ebics/hosts") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val req = call.receiveJson<EbicsHostCreateRequest>()
             val pairA = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
             val pairB = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
@@ -850,7 +815,7 @@ val sandboxApp: Application.() -> Unit = {
          * Show the names of all the Ebics hosts
          */
         get("/admin/ebics/hosts") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             val ebicsHosts = transaction {
                 tech.libeufin.sandbox.EbicsHostEntity.all().map { it.hostId }
             }
@@ -888,7 +853,7 @@ val sandboxApp: Application.() -> Unit = {
          * the default exchange, from a designated/constant customer.
          */
         get("/taler") {
-            requireSuperuser(call.request)
+            call.request.authWithToken(sandboxToken)
             SandboxAssert(
                 currencyEnv != null,
                 "Currency not found.  Logs should have warned"
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index 4d53547..d74d7b2 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -4,6 +4,53 @@ import UtilError
 import io.ktor.http.*
 import io.ktor.request.*
 import logger
+import java.net.URLDecoder
+
+private fun unauthorized(msg: String): UtilError {
+    return UtilError(
+        HttpStatusCode.Unauthorized,
+        msg,
+        LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+    )
+}
+
+/**
+ * Returns the token (including the 'secret-token:' prefix)
+ * from a Authorization header.  Throws exception on malformations
+ * Note, the token gets URL-decoded before being returned.
+ */
+fun extractToken(authHeader: String): String {
+    val headerSplit = authHeader.split(" ", limit = 2)
+    if (headerSplit.elementAtOrNull(0) != "Bearer") throw 
unauthorized("Authorization header does not start with 'Bearer'")
+    val token = headerSplit.elementAtOrNull(1)
+    if (token == null) throw unauthorized("Authorization header did not have 
the token")
+    val tokenSplit = token.split(":", limit = 2)
+    if (tokenSplit.elementAtOrNull(0) != "secret-token")
+        throw unauthorized("Token lacks the 'secret-token:' prefix, see RFC 
8959")
+    val maybeToken = tokenSplit.elementAtOrNull(1)
+    if(maybeToken == null || maybeToken == "")
+        throw unauthorized("Actual token missing after the 'secret-token:' 
prefix")
+    return "${tokenSplit[0]}:${URLDecoder.decode(tokenSplit[1], 
Charsets.UTF_8)}"
+}
+
+/**
+ * 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.
+ */
+fun ApplicationRequest.authWithToken(tokenEnv: String?) {
+    if (tokenEnv == null) {
+        logger.info("Authenticating operation without any env token!")
+        throw unauthorized("Authentication is not available now")
+    }
+    val auth = this.headers[HttpHeaders.Authorization] ?:
+    throw unauthorized("Authorization header was not found in the request")
+    val tokenReq = extractToken(auth)
+    if (tokenEnv != tokenReq) throw unauthorized("Authentication failed, token 
did not match")
+}
 
 fun getHTTPBasicAuthCredentials(request: ApplicationRequest): Pair<String, 
String> {
     val authHeader = getAuthorizationHeader(request)
diff --git a/util/src/test/kotlin/AuthTokenTest.kt 
b/util/src/test/kotlin/AuthTokenTest.kt
new file mode 100644
index 0000000..cf4c8eb
--- /dev/null
+++ b/util/src/test/kotlin/AuthTokenTest.kt
@@ -0,0 +1,34 @@
+import org.junit.Test
+import tech.libeufin.util.extractToken
+import java.lang.Exception
+
+class AuthTokenTest {
+    @Test
+    fun test() {
+        val tok = extractToken("Bearer secret-token:XXX")
+        assert(tok == "secret-token:XXX")
+        val tok_0 = extractToken("Bearer secret-token:XXX%20YYY")
+        assert(tok_0 == "secret-token:XXX YYY")
+        val tok_1 = extractToken("Bearer secret-token:XXX YYY")
+        assert(tok_1 == "secret-token:XXX YYY")
+        val tok_2 = extractToken("Bearer secret-token:XXX ")
+        assert(tok_2 == "secret-token:XXX ")
+
+        val malformedAuths = listOf(
+            "", "XXX", "Bearer", "Bearer ", "Bearer XXX",
+            "BearerXXX", "XXXBearer", "Bearer secret-token",
+            "Bearer secret-token:", " Bearer", " Bearer secret-token:XXX",
+            ":: ::"
+        )
+        for (token in malformedAuths) {
+            try {
+                extractToken(token)
+            } catch (e: Exception) {
+                assert(e is UtilError)
+                continue
+            }
+            println("Error: '$token' made it through")
+            assert(false) // should never arrive here.
+        }
+    }
+}
\ No newline at end of file

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