libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 5e8e7f7fd216a7d9c7f52bca8d53b93d96c29706
parent 093adb84994c8ead295476acc8a136caf12d0f1b
Author: ms <ms@taler.net>
Date:   Thu, 14 Oct 2021 14:24:28 +0200

add --no-auth possibility to facilitate tests

Diffstat:
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 54++++++++++++++++++++++++++++++++++++------------------
Mutil/src/main/kotlin/Config.kt | 9+++++++++
Mutil/src/main/kotlin/HTTP.kt | 27++++++++++++++++++---------
Mutil/src/main/kotlin/UnixDomainSocket.kt | 5++++-
4 files changed, 67 insertions(+), 28 deletions(-)

diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -82,11 +82,11 @@ import javax.xml.bind.JAXBContext import kotlin.system.exitProcess private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox") -private val currencyEnv: String? = getValueFromEnv("LIBEUFIN_SANDBOX_CURRENCY") -private val envName: String? = getValueFromEnv("TALER_ENV_NAME") +private val currencyEnv: String? = System.getenv("LIBEUFIN_SANDBOX_CURRENCY") +private val envName: String? = System.getenv("TALER_ENV_NAME") const val SANDBOX_DB_ENV_VAR_NAME = "LIBEUFIN_SANDBOX_DB_CONNECTION" -// when null, privileged operations turn impossible -private val sandboxToken: String? = getValueFromEnv("LIBEUFIN_SANDBOX_TOKEN") +private val adminPassword: String? = System.getenv("LIBEUFIN_SANDBOX_ADMIN_PASSWORD") +private var WITH_AUTH = true data class SandboxError( val statusCode: HttpStatusCode, @@ -240,6 +240,10 @@ class Serve : CliktCommand("Run sandbox HTTP server") { } } + private val auth by option( + "--auth", + help = "Disable authentication." + ).flag("--no-auth", default = true) private val logLevel by option() private val port by option().int().default(5000) private val withUnixSocket by option( @@ -248,6 +252,7 @@ class Serve : CliktCommand("Run sandbox HTTP server") { ) override fun run() { + WITH_AUTH = auth setLogLevel(logLevel) execThrowableOrTerminate { dbCreateTables(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)) } if (withUnixSocket != null) { @@ -448,6 +453,19 @@ val sandboxApp: Application.() -> Unit = { ) } } + intercept(ApplicationCallPipeline.Setup) { + /** + * Allows disabling authentication during tests. + */ + call.application.apply { + attributes.put(AttributeKey("withAuth"), WITH_AUTH) + } + call.application.apply { + if (adminPassword != null) { + call.attributes.put(AttributeKey("adminPassword"), adminPassword) + } + } + } intercept(ApplicationCallPipeline.Fallback) { if (this.call.response.status() == null) { call.respondText( @@ -467,7 +485,7 @@ val sandboxApp: Application.() -> Unit = { // Respond with the last statement of the requesting account. // Query details in the body. post("/admin/payments/camt") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val body = call.receiveJson<CamtParams>() val bankaccount = getAccountFromLabel(body.bankaccount) if (body.type != 53) throw SandboxError( @@ -490,7 +508,7 @@ val sandboxApp: Application.() -> Unit = { // create a new bank account, no EBICS relation. post("/admin/bank-accounts/{label}") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val body = call.receiveJson<BankAccountInfo>() transaction { BankAccountEntity.new { @@ -507,7 +525,7 @@ val sandboxApp: Application.() -> Unit = { // Information about one bank account. get("/admin/bank-accounts/{label}") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val label = ensureNonNull(call.parameters["label"]) val ret = transaction { val bankAccount = tech.libeufin.sandbox.BankAccountEntity.find { @@ -532,7 +550,7 @@ val sandboxApp: Application.() -> Unit = { // Book one incoming payment for the requesting account. // The debtor is not required to have an account at this Sandbox. post("/admin/bank-accounts/{label}/simulate-incoming-transaction") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val body = call.receiveJson<IncomingPaymentInfo>() // FIXME: generate nicer UUID! val accountLabel = ensureNonNull(call.parameters["label"]) @@ -574,7 +592,7 @@ val sandboxApp: Application.() -> Unit = { // Associates a new bank account with an existing Ebics subscriber. post("/admin/ebics/bank-accounts") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val body = call.receiveJson<BankAccountRequest>() if (!validateBic(body.bic)) { throw SandboxError(io.ktor.http.HttpStatusCode.BadRequest, "invalid BIC (${body.bic})") @@ -606,7 +624,7 @@ val sandboxApp: Application.() -> Unit = { // Information about all the bank accounts. get("/admin/bank-accounts") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val accounts = mutableListOf<BankAccountInfo>() transaction { tech.libeufin.sandbox.BankAccountEntity.all().forEach { @@ -626,7 +644,7 @@ val sandboxApp: Application.() -> Unit = { // Details of all the transactions of one bank account. get("/admin/bank-accounts/{label}/transactions") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val ret = AccountTransactions() transaction { val accountLabel = ensureNonNull(call.parameters["label"]) @@ -669,7 +687,7 @@ val sandboxApp: Application.() -> Unit = { // one bank account. Counterparts do not need to have an account // at this Sandbox. post("/admin/bank-accounts/{label}/generate-transactions") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() transaction { val accountLabel = ensureNonNull(call.parameters["label"]) val account = getBankAccountFromLabel(accountLabel) @@ -720,7 +738,7 @@ val sandboxApp: Application.() -> Unit = { // Creates a new Ebics subscriber. post("/admin/ebics/subscribers") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val body = call.receiveJson<EbicsSubscriberElement>() transaction { tech.libeufin.sandbox.EbicsSubscriberEntity.new { @@ -741,7 +759,7 @@ val sandboxApp: Application.() -> Unit = { // Shows details of all the EBICS subscribers of this Sandbox. get("/admin/ebics/subscribers") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val ret = AdminGetSubscribers() transaction { tech.libeufin.sandbox.EbicsSubscriberEntity.all().forEach { @@ -760,7 +778,7 @@ val sandboxApp: Application.() -> Unit = { // Change keys used in the EBICS communications. post("/admin/ebics/hosts/{hostID}/rotate-keys") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val hostID: String = call.parameters["hostID"] ?: throw SandboxError( io.ktor.http.HttpStatusCode.BadRequest, "host ID missing in URL" ) @@ -787,7 +805,7 @@ val sandboxApp: Application.() -> Unit = { // Create a new EBICS host post("/admin/ebics/hosts") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val req = call.receiveJson<EbicsHostCreateRequest>() val pairA = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048) val pairB = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048) @@ -811,7 +829,7 @@ val sandboxApp: Application.() -> Unit = { // Show the names of all the Ebics hosts get("/admin/ebics/hosts") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() val ebicsHosts = transaction { tech.libeufin.sandbox.EbicsHostEntity.all().map { it.hostId } } @@ -849,7 +867,7 @@ val sandboxApp: Application.() -> Unit = { * the default exchange, from a designated/constant customer. */ get("/taler") { - call.request.authWithToken(sandboxToken) + call.request.basicAuth() SandboxAssert( currencyEnv != null, "Currency not found. Logs should have warned" diff --git a/util/src/main/kotlin/Config.kt b/util/src/main/kotlin/Config.kt @@ -4,6 +4,8 @@ import ch.qos.logback.classic.Level import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.util.ContextInitializer import ch.qos.logback.core.util.Loader +import io.ktor.application.* +import io.ktor.util.* import org.slf4j.LoggerFactory import printLnErr import kotlin.system.exitProcess @@ -50,6 +52,13 @@ fun setLogLevel(logLevel: String?) { } } +internal fun <T : Any>ApplicationCall.getAttribute(name: String): T { + val key = AttributeKey<T>("name") + if (!this.attributes.contains(key)) + throw internalServerError("Attribute $name not found along the call.") + return this.attributes[key] +} + fun getValueFromEnv(varName: String): String? { val ret = System.getenv(varName) if (ret.isNullOrBlank() or ret.isNullOrEmpty()) { diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt @@ -16,6 +16,8 @@ private fun unauthorized(msg: String): UtilError { ) } + + /** * Returns the token (including the 'secret-token:' prefix) * from a Authorization header. Throws exception on malformations @@ -35,7 +37,7 @@ fun extractToken(authHeader: String): String { return "${tokenSplit[0]}:${URLDecoder.decode(tokenSplit[1], Charsets.UTF_8)}" } -private fun internalServerError( +internal fun internalServerError( reason: String, libeufinErrorCode: LibeufinErrorCode? = LibeufinErrorCode.LIBEUFIN_EC_NONE ): UtilError { @@ -86,15 +88,22 @@ fun ApplicationRequest.getBaseUrl(): String { * @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") +fun ApplicationRequest.basicAuth() { + val withAuth = this.call.getAttribute<Boolean>("withAuth") + if (!withAuth) { + logger.info("Authentication is disabled - assuming tests currently running.") + return + } + val credentials = getHTTPBasicAuthCredentials(this) + if (credentials.first == "admin") { + val adminPassword = this.call.getAttribute<String>("adminPassword") + if (credentials.second != adminPassword) throw unauthorized( + "Admin authentication failed" + ) } - 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") + /** + * TODO: extract customer hashed password from the database and check. + */ } fun getHTTPBasicAuthCredentials(request: ApplicationRequest): Pair<String, String> { diff --git a/util/src/main/kotlin/UnixDomainSocket.kt b/util/src/main/kotlin/UnixDomainSocket.kt @@ -31,7 +31,10 @@ import java.io.ByteArrayInputStream import java.io.InputStream import java.nio.charset.Charset -fun startServer(unixSocketPath: String, app: Application.() -> Unit) { +fun startServer( + unixSocketPath: String, + app: Application.() -> Unit +) { val boss = EpollEventLoopGroup() val worker = EpollEventLoopGroup() try {