[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: Provide authentication to Sandbox
From: |
gnunet |
Subject: |
[libeufin] branch master updated: Provide authentication to Sandbox |
Date: |
Fri, 17 Sep 2021 15:46:47 +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 42c62f2 Provide authentication to Sandbox
42c62f2 is described below
commit 42c62f26b4fe0e5cc2fc3f29f8b79f25b2c49ee0
Author: ms <ms@taler.net>
AuthorDate: Fri Sep 17 15:46:37 2021 +0200
Provide authentication to Sandbox
---
nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt | 44 ++++-----------
.../src/main/kotlin/tech/libeufin/sandbox/Auth.kt | 45 ++++++++++++++++
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 63 ++++++++++++++++++++++
util/src/main/kotlin/CryptoUtil.kt | 14 +++++
util/src/main/kotlin/EbicsOrderUtil.kt | 2 +-
util/src/main/kotlin/Errors.kt | 7 ++-
util/src/main/kotlin/HTTP.kt | 43 +++++++++++++++
util/src/main/kotlin/LibeufinErrorCodes.kt | 14 ++++-
util/src/main/kotlin/XMLUtil.kt | 3 +-
util/src/main/kotlin/strings.kt | 6 +--
10 files changed, 198 insertions(+), 43 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt
index 9d222e9..639abb3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt
@@ -1,5 +1,6 @@
package tech.libeufin.nexus
+import UtilError
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
@@ -7,54 +8,27 @@ import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import tech.libeufin.nexus.server.Permission
import tech.libeufin.nexus.server.PermissionQuery
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.base64ToBytes
-import tech.libeufin.util.constructXml
-
+import tech.libeufin.util.*
/**
- * This helper function parses a Authorization:-header line, decode the
credentials
- * and returns a pair made of username and hashed (sha256) password. The
hashed value
- * will then be compared with the one kept into the database.
- */
-private fun extractUserAndPassword(authorizationHeader: String): Pair<String,
String> {
- logger.debug("Authenticating: $authorizationHeader")
- val (username, password) = try {
- val split = authorizationHeader.split(" ")
- val plainUserAndPass = String(base64ToBytes(split[1]), Charsets.UTF_8)
- plainUserAndPass.split(":")
- } catch (e: java.lang.Exception) {
- throw NexusError(
- HttpStatusCode.BadRequest,
- "invalid Authorization:-header received"
- )
- }
- return Pair(username, password)
-}
-
-
-/**
- * Test HTTP basic auth. Throws error if password is wrong,
+ * HTTP basic auth. Throws error if password is wrong,
* and makes sure that the user exists in the system.
*
* @return user entity
*/
fun authenticateRequest(request: ApplicationRequest): NexusUserEntity {
return transaction {
- val authorization = request.headers["Authorization"]
- val headerLine = if (authorization == null) throw NexusError(
- HttpStatusCode.BadRequest, "Authorization header not found"
- ) else authorization
- val (username, password) = extractUserAndPassword(headerLine)
+ val (username, password) = getHTTPBasicAuthCredentials(request)
val user = NexusUserEntity.find {
NexusUsersTable.username eq username
}.firstOrNull()
if (user == null) {
- throw NexusError(HttpStatusCode.Unauthorized, "Unknown user
'$username'")
- }
- if (!CryptoUtil.checkpw(password, user.passwordHash)) {
- throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
+ throw UtilError(HttpStatusCode.Unauthorized,
+ "Unknown user '$username'",
+ LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+ )
}
+ CryptoUtil.checkPwOrThrow(password, username)
user
}
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
new file mode 100644
index 0000000..b86bdf7
--- /dev/null
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Auth.kt
@@ -0,0 +1,45 @@
+package tech.libeufin.sandbox
+
+import UtilError
+import io.ktor.http.*
+import io.ktor.request.*
+import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
+import org.jetbrains.exposed.sql.transactions.transaction
+import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.LibeufinErrorCode
+import tech.libeufin.util.getHTTPBasicAuthCredentials
+
+
+/**
+ * HTTP basic auth. Throws error if password is wrong,
+ * and makes sure that the user exists in the system.
+ *
+ * @return user entity
+ */
+fun authenticateRequest(request: ApplicationRequest): SandboxUserEntity {
+ return transaction {
+ val (username, password) = getHTTPBasicAuthCredentials(request)
+ val user = SandboxUserEntity.find {
+ SandboxUsersTable.username eq username
+ }.firstOrNull()
+ if (user == null) {
+ throw UtilError(
+ HttpStatusCode.Unauthorized,
+ "Unknown user '$username'",
+ LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+ )
+ }
+ CryptoUtil.checkPwOrThrow(password, username)
+ user
+ }
+}
+
+fun requireSuperuser(request: ApplicationRequest): SandboxUserEntity {
+ return transaction {
+ val user = authenticateRequest(request)
+ if (!user.superuser) {
+ throw SandboxError(HttpStatusCode.Forbidden, "must be superuser")
+ }
+ user
+ }
+}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 8c54cef..4a01d5f 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -57,6 +57,7 @@ import
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
import java.time.Instant
import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.core.subcommands
@@ -71,6 +72,7 @@ import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.util.date.*
+import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import tech.libeufin.util.*
import tech.libeufin.util.ebics_h004.EbicsResponse
import tech.libeufin.util.ebics_h004.EbicsTypes
@@ -92,6 +94,33 @@ 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))
+ }
+ transaction {
+ val hashedPw = CryptoUtil.hashpw(password)
+ val user = SandboxUserEntity.find { SandboxUsersTable.username eq
username }.firstOrNull()
+ 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 {
@@ -358,6 +387,7 @@ class SandboxCommand : CliktCommand(invokeWithoutSubcommand
= true, printHelpOnE
fun main(args: Array<String>) {
SandboxCommand().subcommands(
+ Superuser(),
Serve(),
ResetTables(),
Config(),
@@ -496,6 +526,9 @@ fun serverMain(dbName: String, port: Int) {
return@get
} */
+ /*
+ FIXME: not implemented.
+
post("/register") {
// how to read form-POSTed values?
val username = "fixme"
@@ -531,6 +564,11 @@ fun serverMain(dbName: String, port: Int) {
return@post
}
+ */
+
+ /*
+ FIXME: will likely be replaced by the Single Page Application
+
get("/jinja-test") {
val template = Resources.toString(
Resources.getResource("templates/hello.html"),
@@ -542,6 +580,12 @@ fun serverMain(dbName: String, port: Int) {
return@get
}
+ */
+
+ /*
+
+ FIXME: not used
+
authenticate("auth-form") {
get("/profile") {
val userSession = call.principal<UserIdPrincipal>()
@@ -551,12 +595,18 @@ fun serverMain(dbName: String, port: Int) {
}
}
+ */
+
+ /*
+ FIXME: not used
+
static("/static") {
/**
* Here Sandbox will serve the CSS files.
*/
resources("static")
}
+ */
get("/") {
call.respondText("Hello, this is Sandbox\n",
ContentType.Text.Plain)
@@ -574,6 +624,7 @@ fun serverMain(dbName: String, port: Int) {
* requesting account.
*/
post("/admin/payments/camt") {
+ requireSuperuser(call.request)
val body = call.receiveJson<CamtParams>()
val bankaccount = getAccountFromLabel(body.bankaccount)
if(body.type != 53) throw SandboxError(
@@ -595,6 +646,7 @@ fun serverMain(dbName: String, port: Int) {
}
post("/admin/bank-accounts/{label}") {
+ requireSuperuser(call.request)
val body = call.receiveJson<BankAccountInfo>()
transaction {
BankAccountEntity.new {
@@ -610,6 +662,7 @@ fun serverMain(dbName: String, port: Int) {
}
get("/admin/bank-accounts/{label}") {
+ requireSuperuser(call.request)
val label = ensureNonNull(call.parameters["label"])
val ret = transaction {
val bankAccount = BankAccountEntity.find {
@@ -632,6 +685,7 @@ fun serverMain(dbName: String, port: Int) {
}
post("/admin/bank-accounts/{label}/simulate-incoming-transaction")
{
+ requireSuperuser(call.request)
val body = call.receiveJson<IncomingPaymentInfo>()
// FIXME: generate nicer UUID!
val accountLabel = ensureNonNull(call.parameters["label"])
@@ -674,6 +728,7 @@ fun serverMain(dbName: String, port: Int) {
* Associates a new bank account with an existing Ebics subscriber.
*/
post("/admin/ebics/bank-accounts") {
+ requireSuperuser(call.request)
val body = call.receiveJson<BankAccountRequest>()
if (!validateBic(body.bic)) {
throw SandboxError(HttpStatusCode.BadRequest, "invalid BIC
(${body.bic})")
@@ -703,6 +758,7 @@ fun serverMain(dbName: String, port: Int) {
return@post
}
get("/admin/bank-accounts") {
+ requireSuperuser(call.request)
val accounts = mutableListOf<BankAccountInfo>()
transaction {
BankAccountEntity.all().forEach {
@@ -720,6 +776,7 @@ fun serverMain(dbName: String, port: Int) {
call.respond(accounts)
}
get("/admin/bank-accounts/{label}/transactions") {
+ requireSuperuser(call.request)
val ret = AccountTransactions()
transaction {
val accountLabel = ensureNonNull(call.parameters["label"])
@@ -758,6 +815,7 @@ fun serverMain(dbName: String, port: Int) {
call.respond(ret)
}
post("/admin/bank-accounts/{label}/generate-transactions") {
+ requireSuperuser(call.request)
transaction {
val accountLabel = ensureNonNull(call.parameters["label"])
val account = getBankAccountFromLabel(accountLabel)
@@ -809,6 +867,7 @@ fun serverMain(dbName: String, port: Int) {
* Creates a new Ebics subscriber.
*/
post("/admin/ebics/subscribers") {
+ requireSuperuser(call.request)
val body = call.receiveJson<EbicsSubscriberElement>()
transaction {
EbicsSubscriberEntity.new {
@@ -830,6 +889,7 @@ fun serverMain(dbName: String, port: Int) {
* Shows all the Ebics subscribers' details.
*/
get("/admin/ebics/subscribers") {
+ requireSuperuser(call.request)
val ret = AdminGetSubscribers()
transaction {
EbicsSubscriberEntity.all().forEach {
@@ -846,6 +906,7 @@ fun serverMain(dbName: String, port: Int) {
return@get
}
post("/admin/ebics/hosts/{hostID}/rotate-keys") {
+ requireSuperuser(call.request)
val hostID: String = call.parameters["hostID"] ?: throw
SandboxError(
HttpStatusCode.BadRequest, "host ID missing in URL"
)
@@ -874,6 +935,7 @@ fun serverMain(dbName: String, port: Int) {
* Creates a new EBICS host.
*/
post("/admin/ebics/hosts") {
+ requireSuperuser(call.request)
val req = call.receiveJson<EbicsHostCreateRequest>()
val pairA = CryptoUtil.generateRsaKeyPair(2048)
val pairB = CryptoUtil.generateRsaKeyPair(2048)
@@ -899,6 +961,7 @@ fun serverMain(dbName: String, port: Int) {
* Show the names of all the Ebics hosts
*/
get("/admin/ebics/hosts") {
+ requireSuperuser(call.request)
val ebicsHosts = transaction {
EbicsHostEntity.all().map { it.hostId }
}
diff --git a/util/src/main/kotlin/CryptoUtil.kt
b/util/src/main/kotlin/CryptoUtil.kt
index b92b626..6c5f99f 100644
--- a/util/src/main/kotlin/CryptoUtil.kt
+++ b/util/src/main/kotlin/CryptoUtil.kt
@@ -19,6 +19,8 @@
package tech.libeufin.util
+import UtilError
+import io.ktor.http.*
import net.taler.wallet.crypto.Base32Crockford
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.io.ByteArrayOutputStream
@@ -308,6 +310,18 @@ object CryptoUtil {
return "sha256-salted\$$salt\$$pwh"
}
+ /**
+ * Throws error when credentials don't match. Only returns in case of
success.
+ */
+ fun checkPwOrThrow(pw: String, storedPwHash: String): Boolean {
+ if(!this.checkpw(pw, storedPwHash)) throw UtilError(
+ HttpStatusCode.Forbidden,
+ "Credentials did not match",
+ LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+ )
+ return true
+ }
+
fun checkpw(pw: String, storedPwHash: String): Boolean {
val components = storedPwHash.split('$')
if (components.size < 2) {
diff --git a/util/src/main/kotlin/EbicsOrderUtil.kt
b/util/src/main/kotlin/EbicsOrderUtil.kt
index bcdc836..64f1fd3 100644
--- a/util/src/main/kotlin/EbicsOrderUtil.kt
+++ b/util/src/main/kotlin/EbicsOrderUtil.kt
@@ -55,7 +55,7 @@ object EbicsOrderUtil {
val rng = SecureRandom()
val res = ByteArray(16)
rng.nextBytes(res)
- return res.toHexString().toUpperCase()
+ return res.toHexString().uppercase()
}
/**
diff --git a/util/src/main/kotlin/Errors.kt b/util/src/main/kotlin/Errors.kt
index 73ad892..55ad871 100644
--- a/util/src/main/kotlin/Errors.kt
+++ b/util/src/main/kotlin/Errors.kt
@@ -1,6 +1,9 @@
import io.ktor.http.*
+import tech.libeufin.util.LibeufinErrorCode
import tech.libeufin.util.TalerErrorCode
import kotlin.system.exitProcess
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
/*
* This file is part of LibEuFin.
@@ -21,10 +24,12 @@ import kotlin.system.exitProcess
* <http://www.gnu.org/licenses/>
*/
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
+
data class UtilError(
val statusCode: HttpStatusCode,
val reason: String,
- val ec: TalerErrorCode?
+ val ec: LibeufinErrorCode?
) :
Exception("$reason (HTTP status $statusCode)")
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
new file mode 100644
index 0000000..69ed7a7
--- /dev/null
+++ b/util/src/main/kotlin/HTTP.kt
@@ -0,0 +1,43 @@
+package tech.libeufin.util
+
+import UtilError
+import io.ktor.http.*
+import io.ktor.request.*
+import logger
+
+fun getHTTPBasicAuthCredentials(request: ApplicationRequest): Pair<String,
String> {
+ val authHeader = getAuthorizationHeader(request)
+ return extractUserAndPassword(authHeader)
+}
+
+/**
+ * Extracts the Authorization:-header line and throws error if not found.
+ */
+fun getAuthorizationHeader(request: ApplicationRequest): String {
+ val authorization = request.headers["Authorization"]
+ return authorization ?: throw UtilError(
+ HttpStatusCode.BadRequest, "Authorization header not found",
+ LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+ )
+}
+
+/**
+ * This helper function parses a Authorization:-header line, decode the
credentials
+ * and returns a pair made of username and hashed (sha256) password. The
hashed value
+ * will then be compared with the one kept into the database.
+ */
+fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
+ logger.debug("Authenticating: $authorizationHeader")
+ val (username, password) = try {
+ val split = authorizationHeader.split(" ")
+ val plainUserAndPass = String(base64ToBytes(split[1]), Charsets.UTF_8)
+ plainUserAndPass.split(":")
+ } catch (e: java.lang.Exception) {
+ throw UtilError(
+ HttpStatusCode.BadRequest,
+ "invalid Authorization:-header received",
+ LibeufinErrorCode.LIBEUFIN_EC_AUTHENTICATION_FAILED
+ )
+ }
+ return Pair(username, password)
+}
diff --git a/util/src/main/kotlin/LibeufinErrorCodes.kt
b/util/src/main/kotlin/LibeufinErrorCodes.kt
index f1c1828..3bc5fbc 100644
--- a/util/src/main/kotlin/LibeufinErrorCodes.kt
+++ b/util/src/main/kotlin/LibeufinErrorCodes.kt
@@ -48,5 +48,17 @@ enum class LibeufinErrorCode(val code: Int) {
* A bank's invariant is not holding anymore. For example, a customer's
* balance doesn't match the history of their bank account.
*/
- LIBEUFIN_EC_INCONSISTENT_STATE(3)
+ LIBEUFIN_EC_INCONSISTENT_STATE(3),
+
+ /**
+ * An access was forbidden due to wrong credentials.
+ */
+ LIBEUFIN_EC_AUTHENTICATION_FAILED(4),
+
+ /**
+ * A parameter in the request was malformed.
+ * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+ * (A value of 0 indicates that the error is generated client-side).
+ */
+ LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED(5),
}
\ No newline at end of file
diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt
index fcd12f9..5e0780b 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/util/src/main/kotlin/XMLUtil.kt
@@ -61,8 +61,7 @@ import javax.xml.validation.Validator
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
-
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
+import logger
class DefaultNamespaces : NamespacePrefixMapper() {
override fun getPreferredPrefix(namespaceUri: String?, suggestion:
String?, requirePrefix: Boolean): String? {
diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt
index 7fd464c..e3f6565 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/util/src/main/kotlin/strings.kt
@@ -96,7 +96,7 @@ fun chunkString(input: String): String {
}
ret.append(input[i])
}
- return ret.toString().toUpperCase()
+ return ret.toString().uppercase()
}
data class AmountWithCurrency(
@@ -109,7 +109,7 @@ fun parseDecimal(decimalStr: String): BigDecimal {
throw UtilError(
HttpStatusCode.BadRequest,
"Bad string amount given: $decimalStr",
- TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MALFORMED
+ LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
)
return try {
BigDecimal(decimalStr)
@@ -117,7 +117,7 @@ fun parseDecimal(decimalStr: String): BigDecimal {
throw UtilError(
HttpStatusCode.BadRequest,
"Bad string amount given: $decimalStr",
- TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MALFORMED
+ LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED
)
}
}
--
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: Provide authentication to Sandbox,
gnunet <=