[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: Implment token-based authentication.,
gnunet <=