libeufin

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

commit e878b4bf8dcab41a89878f7aed515c4854d7c69c
parent 8c15ef1de61e9c6a49961a3d7fae95f5efbef6ac
Author: MS <ms@taler.net>
Date:   Thu, 19 Jan 2023 16:26:26 +0100

Circuit API: fix UUID parsing

Diffstat:
Mnexus/src/test/kotlin/SandboxAccessApiTest.kt | 1+
Mnexus/src/test/kotlin/SandboxCircuitApiTest.kt | 16++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt | 18++++--------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt | 3++-
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 20++++----------------
Mutil/src/main/kotlin/HTTP.kt | 2+-
Mutil/src/main/kotlin/strings.kt | 14++++++++++++++
7 files changed, 42 insertions(+), 32 deletions(-)

diff --git a/nexus/src/test/kotlin/SandboxAccessApiTest.kt b/nexus/src/test/kotlin/SandboxAccessApiTest.kt @@ -61,6 +61,7 @@ class SandboxAccessApiTest { } } } + // Tests for #7482 @Test fun highAmountWithdraw() { diff --git a/nexus/src/test/kotlin/SandboxCircuitApiTest.kt b/nexus/src/test/kotlin/SandboxCircuitApiTest.kt @@ -28,6 +28,22 @@ class SandboxCircuitApiTest { } } @Test + fun badUuidTest() { + withTestDatabase { + prepSandboxDb() + testApplication { + application(sandboxApp) + val R = client.post("/demobanks/default/circuit-api/cashouts/---invalid_UUID---/confirm") { + expectSuccess = false + basicAuth("foo", "foo") + contentType(ContentType.Application.Json) + setBody("{\"tan\":\"foo\"}") + } + assert(R.status.value == HttpStatusCode.BadRequest.value) + } + } + } + @Test fun contactDataValidation() { // Phone number. assert(checkPhoneNumber("+987")) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt @@ -180,12 +180,7 @@ fun circuitApi(circuitRoute: Route) { call.request.basicAuth() // both admin and author allowed val arg = call.getUriComponent("uuid") // Parse and check the UUID. - val maybeUuid = try { - UUID.fromString(arg) - } catch (e: Exception) { - logger.error(e.message) - throw badRequest("The cash-out UUID is invalid: $arg") // global handler logs this. - } + val maybeUuid = parseUuid(arg) val maybeOperation = transaction { CashoutOperationEntity.find { uuid eq maybeUuid }.firstOrNull() } @@ -210,10 +205,10 @@ fun circuitApi(circuitRoute: Route) { if (user == "admin" || user == "bank") throw conflict("Institutional user '$user' shouldn't confirm any cash-out.") // Get the operation identifier. - val operationUuid = call.getUriComponent("uuid") + val operationUuid = parseUuid(call.getUriComponent("uuid")) val op = transaction { CashoutOperationEntity.find { - uuid eq UUID.fromString(operationUuid) + uuid eq operationUuid }.firstOrNull() } // 404 if the operation is not found. @@ -260,12 +255,7 @@ fun circuitApi(circuitRoute: Route) { call.request.basicAuth() // both admin and author val operationUuid = call.getUriComponent("uuid") // Parse and check the UUID. - val maybeUuid = try { - UUID.fromString(operationUuid) - } catch (e: Exception) { - logger.error(e.message) - throw badRequest("The cash-out UUID is invalid: $operationUuid") - } + val maybeUuid = parseUuid(operationUuid) // Get the operation from the database. val maybeOperation = transaction { CashoutOperationEntity.find { uuid eq maybeUuid }.firstOrNull() diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Helpers.kt @@ -278,9 +278,10 @@ fun getDefaultDemobank(): DemobankConfigEntity { } fun getWithdrawalOperation(opId: String): TalerWithdrawalEntity { + val uuid = parseUuid(opId) return transaction { TalerWithdrawalEntity.find { - TalerWithdrawalsTable.wopid eq UUID.fromString(opId) + TalerWithdrawalsTable.wopid eq uuid }.firstOrNull() ?: throw SandboxError( HttpStatusCode.NotFound, "Withdrawal operation $opId not found." ) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -37,8 +37,6 @@ import execThrowableOrTerminate import io.ktor.server.application.* import io.ktor.http.* import io.ktor.serialization.jackson.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* import io.ktor.server.plugins.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.statuspages.* @@ -1172,18 +1170,13 @@ val sandboxApp: Application.() -> Unit = { } post("/withdrawal-operation/{wopid}") { val arg = ensureNonNull(call.parameters["wopid"]) - val maybeWithdrawalUUid = try { - java.util.UUID.fromString(arg) - } catch (e: Exception) { - logger.debug(e.message) - throw badRequest("Withdrawal operation UUID was invalid: $arg") - } + val withdrawalUuid = parseUuid(arg) val body = call.receive<TalerWithdrawalSelection>() val transferDone = transaction { val wo = TalerWithdrawalEntity.find { - TalerWithdrawalsTable.wopid eq maybeWithdrawalUUid + TalerWithdrawalsTable.wopid eq withdrawalUuid }.firstOrNull() ?: throw SandboxError( - HttpStatusCode.NotFound, "Withdrawal operation $maybeWithdrawalUUid not found." + HttpStatusCode.NotFound, "Withdrawal operation $withdrawalUuid not found." ) if (wo.confirmationDone) { return@transaction true @@ -1216,12 +1209,7 @@ val sandboxApp: Application.() -> Unit = { } get("/withdrawal-operation/{wopid}") { val arg = ensureNonNull(call.parameters["wopid"]) - val maybeWithdrawalUuid = try { - java.util.UUID.fromString(arg) - } catch (e: Exception) { - logger.debug(e.message) - throw badRequest("Withdrawal UUID invalid: $arg") - } + val maybeWithdrawalUuid = parseUuid(arg) val maybeWithdrawalOp = transaction { TalerWithdrawalEntity.find { TalerWithdrawalsTable.wopid eq maybeWithdrawalUuid diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt @@ -119,7 +119,7 @@ fun ApplicationRequest.getBaseUrl(): String { */ fun ApplicationCall.getUriComponent(name: String): String { val ret: String? = this.parameters[name] - if (ret == null) throw internalServerError("Component $name not found in URI") + if (ret == null) throw badRequest("Component $name not found in URI") return ret } diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt @@ -167,6 +167,7 @@ fun sanityCheckOrThrow(credentials: Pair<String, String>) { LibeufinErrorCode.LIBEUFIN_EC_GENERIC_PARAMETER_MALFORMED ) } + /** * Sanity-check user's credentials. */ @@ -175,4 +176,17 @@ fun sanityCheckCredentials(credentials: Pair<String, String>): Boolean { if (!allowedChars.matches(credentials.first)) return false if (!allowedChars.matches(credentials.second)) return false return true +} + +/** + * Parses string into java.util.UUID format or throws 400 Bad Request. + * The output is usually consumed in database queries. + */ +fun parseUuid(maybeUuid: String): UUID { + val uuid = try { + UUID.fromString(maybeUuid) + } catch (e: Exception) { + throw badRequest("$maybeUuid is invalid.") + } + return uuid } \ No newline at end of file