commit 3d1dfce83bf74917886a5f3cc55aa7f97b74aac4 parent 3046601e239d774716527d4a0a34f585ac5ade79 Author: Antoine A <> Date: Tue, 13 Feb 2024 20:35:04 +0100 Code cleanup Diffstat:
91 files changed, 610 insertions(+), 618 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt @@ -21,15 +21,15 @@ * that are typically requested by wallets. */ package tech.libeufin.bank +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.http.* +import tech.libeufin.bank.db.AbortResult +import tech.libeufin.bank.db.Database +import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalSelectionResult import tech.libeufin.common.TalerErrorCode -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.WithdrawalDAO.* -import java.lang.AssertionError fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { get("/taler-integration/config") { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -18,14 +18,12 @@ */ package tech.libeufin.bank -import tech.libeufin.common.* import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import tech.libeufin.common.DatabaseConfig -import java.nio.file.* -import kotlin.io.path.* import org.slf4j.Logger import org.slf4j.LoggerFactory +import tech.libeufin.common.* +import java.nio.file.Path private val logger: Logger = LoggerFactory.getLogger("libeufin-bank") @@ -89,11 +87,11 @@ fun TalerConfig.loadServerConfig(): ServerConfig { fun TalerConfig.loadBankConfig(): BankConfig { val regionalCurrency = requireString("libeufin-bank", "currency") - var fiatCurrency: String? = null; + var fiatCurrency: String? = null var fiatCurrencySpec: CurrencySpecification? = null - val allowConversion = lookupBoolean("libeufin-bank", "allow_conversion") ?: false; + val allowConversion = lookupBoolean("libeufin-bank", "allow_conversion") ?: false if (allowConversion) { - fiatCurrency = requireString("libeufin-bank", "fiat_currency"); + fiatCurrency = requireString("libeufin-bank", "fiat_currency") fiatCurrencySpec = currencySpecificationFor(fiatCurrency) } val tanChannels = buildMap { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt @@ -18,23 +18,23 @@ */ package tech.libeufin.bank -import tech.libeufin.common.* +import tech.libeufin.common.ConfigSource import java.time.Duration // Config val BANK_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-bank", "libeufin-bank") // TAN -const val TAN_RETRY_COUNTER: Int = 3; +const val TAN_RETRY_COUNTER: Int = 3 val TAN_VALIDITY_PERIOD: Duration = Duration.ofHours(1) val TAN_RETRANSMISSION_PERIOD: Duration = Duration.ofMinutes(1) // Token -val TOKEN_DEFAULT_DURATION: java.time.Duration = Duration.ofDays(1L) +val TOKEN_DEFAULT_DURATION: Duration = Duration.ofDays(1L) // Account val RESERVED_ACCOUNTS = setOf("admin", "bank") -const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5; +const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5 // Security const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB diff --git a/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt @@ -23,11 +23,12 @@ import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import java.util.* -import tech.libeufin.common.* -import tech.libeufin.bank.auth.* -import tech.libeufin.bank.db.ConversionDAO.* -import tech.libeufin.bank.db.* +import tech.libeufin.bank.auth.authAdmin +import tech.libeufin.bank.db.ConversionDAO +import tech.libeufin.bank.db.ConversionDAO.ConversionResult +import tech.libeufin.bank.db.Database +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode fun Routing.conversionApi(db: Database, ctx: BankConfig) = conditional(ctx.allowConversion) { get("/conversion-info/config") { @@ -108,7 +109,7 @@ fun Routing.conversionApi(db: Database, ctx: BankConfig) = conditional(ctx.allow for (fiatAmount in sequenceOf(req.cashout_fee, req.cashout_tiny_amount, req.cashin_min_amount)) { ctx.checkFiatCurrency(fiatAmount) } - db.conversion.updateConfig(req); + db.conversion.updateConfig(req) call.respond(HttpStatusCode.NoContent) } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -23,27 +23,27 @@ import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import java.time.Duration -import java.time.Instant -import java.time.temporal.ChronoUnit -import java.util.* -import kotlin.random.Random -import kotlinx.serialization.json.Json import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.await import kotlinx.coroutines.withContext import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.bank.* import tech.libeufin.bank.auth.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.TanDAO.* +import tech.libeufin.bank.db.AbortResult import tech.libeufin.bank.db.AccountDAO.* -import tech.libeufin.bank.db.CashoutDAO.* -import tech.libeufin.bank.db.ExchangeDAO.* -import tech.libeufin.bank.db.TransactionDAO.* -import tech.libeufin.bank.db.WithdrawalDAO.* +import tech.libeufin.bank.db.CashoutDAO.CashoutCreationResult +import tech.libeufin.bank.db.Database +import tech.libeufin.bank.db.TanDAO.TanSendResult +import tech.libeufin.bank.db.TanDAO.TanSolveResult +import tech.libeufin.bank.db.TransactionDAO.BankTransactionResult +import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalConfirmationResult +import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalCreationResult import tech.libeufin.common.* +import java.time.Duration +import java.time.Instant +import java.time.temporal.ChronoUnit +import java.util.* +import kotlin.random.Random private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-api") @@ -293,7 +293,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { authAdmin(db, TokenScope.readwrite, !ctx.allowRegistration) { post("/accounts") { val req = call.receive<RegisterAccountRequest>() - val (result, internalPayto) = createAccount(db, ctx, req, isAdmin); + val (result, internalPayto) = createAccount(db, ctx, req, isAdmin) when (result) { AccountCreationResult.BonusBalanceInsufficient -> throw conflict( "Insufficient admin funds to grant bonus", diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt @@ -19,11 +19,13 @@ package tech.libeufin.bank import io.ktor.http.* +import io.ktor.server.application.* import io.ktor.server.response.* -import io.ktor.server.application.ApplicationCall -import io.ktor.util.AttributeKey +import io.ktor.util.* import kotlinx.serialization.Serializable -import tech.libeufin.common.* +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode + /** * Convenience type to throw errors along the bank activity * and that is meant to be caught by Ktor and responded to the @@ -50,7 +52,7 @@ data class TalerError( val detail: String? = null ) -private val LOG_MSG = AttributeKey<String>("log_msg"); +private val LOG_MSG = AttributeKey<String>("log_msg") fun ApplicationCall.logMsg(): String? = attributes.getOrNull(LOG_MSG) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -21,41 +21,46 @@ package tech.libeufin.bank import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.subcommands -import com.github.ajalt.clikt.parameters.types.* -import com.github.ajalt.clikt.parameters.arguments.* +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.convert +import com.github.ajalt.clikt.parameters.arguments.optional +import com.github.ajalt.clikt.parameters.groups.OptionGroup +import com.github.ajalt.clikt.parameters.groups.cooccurring +import com.github.ajalt.clikt.parameters.groups.provideDelegate import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.types.boolean import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.http.content.* import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* import io.ktor.server.engine.* +import io.ktor.server.http.content.* import io.ktor.server.netty.* import io.ktor.server.plugins.* import io.ktor.server.plugins.callloging.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* -import io.ktor.server.plugins.statuspages.* import io.ktor.server.plugins.forwardedheaders.* +import io.ktor.server.plugins.statuspages.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.utils.io.* -import java.time.Duration -import java.util.zip.DataFormatException -import java.util.zip.Inflater -import java.sql.SQLException -import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import org.postgresql.util.PSQLState import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level -import org.postgresql.util.PSQLState import tech.libeufin.bank.db.AccountDAO.* -import tech.libeufin.bank.db.* +import tech.libeufin.bank.db.Database import tech.libeufin.common.* -import kotlin.io.path.* +import java.sql.SQLException +import java.util.zip.DataFormatException +import java.util.zip.Inflater +import kotlin.io.path.Path +import kotlin.io.path.exists +import kotlin.io.path.readText private val logger: Logger = LoggerFactory.getLogger("libeufin-bank") // Dirty local variable to stop the server in test TODO remove this ugly hack @@ -69,7 +74,7 @@ val bodyPlugin = createApplicationPlugin("BodyLimitAndDecompression") { // TODO check content length as an optimisation transformBody { body -> val bytes = ByteArray(MAX_BODY_LENGTH.toInt() + 1) - var read = 0; + var read = 0 when (val encoding = call.request.headers[HttpHeaders.ContentEncoding]) { "deflate" -> { // Decompress and check decompressed length @@ -95,7 +100,7 @@ val bodyPlugin = createApplicationPlugin("BodyLimitAndDecompression") { // Check body length while (true) { val new = body.readAvailable(bytes, read, bytes.size - read) - if (new == -1) break; // Channel is closed + if (new == -1) break // Channel is closed read += new if (read > MAX_BODY_LENGTH) throw badRequest("Body is suspiciously big > $MAX_BODY_LENGTH B") @@ -239,7 +244,7 @@ class BankDbInit : CliktCommand("Initialize the libeufin-bank database", name = override fun run() = cliCmd(logger, common.log) { val config = talerConfig(common.config) val cfg = config.loadDbConfig() - val ctx = config.loadBankConfig(); + val ctx = config.loadBankConfig() Database(cfg.dbConnStr, ctx.regionalCurrency, ctx.fiatCurrency).use { db -> runBlocking { db.conn { conn -> @@ -480,7 +485,7 @@ class CreateAccount : CliktCommand( ) } req?.let { - val (result, internalPayto) = createAccount(db, ctx, req, true); + val (result, internalPayto) = createAccount(db, ctx, req, true) when (result) { AccountCreationResult.BonusBalanceInsufficient -> throw Exception("Insufficient admin funds to grant bonus") diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt b/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt @@ -21,7 +21,7 @@ package tech.libeufin.bank private val PATTERN = Regex("[a-z0-9A-Z]{52}") fun parseIncomingTxMetadata(subject: String): EddsaPublicKey? { - val match = PATTERN.find(subject)?.value ?: return null; + val match = PATTERN.find(subject)?.value ?: return null try { return EddsaPublicKey(match) } catch (e: Exception) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Params.kt b/bank/src/main/kotlin/tech/libeufin/bank/Params.kt @@ -20,14 +20,11 @@ package tech.libeufin.bank import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.plugins.* -import io.ktor.server.request.* -import io.ktor.server.util.* -import java.time.* -import java.time.temporal.* -import java.util.* -import tech.libeufin.common.* +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.temporal.TemporalAdjusters fun Parameters.expect(name: String): String = get(name) ?: throw badRequest("Missing '$name' parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING) @@ -53,10 +50,10 @@ data class MonitorParams( val which: Int? ) { companion object { - val names = Timeframe.values().map { it.name } + val names = Timeframe.entries.map { it.name } val names_fmt = names.joinToString() fun extract(params: Parameters): MonitorParams { - val raw = params.get("timeframe") ?: "hour"; + val raw = params.get("timeframe") ?: "hour" if (!names.contains(raw)) { throw badRequest("Param 'timeframe' must be one of $names_fmt", TalerErrorCode.GENERIC_PARAMETER_MALFORMED) } @@ -150,10 +147,10 @@ data class StatusParams( val old_state: WithdrawalStatus ) { companion object { - val names = WithdrawalStatus.values().map { it.name } + val names = WithdrawalStatus.entries.map { it.name } val names_fmt = names.joinToString() fun extract(params: Parameters): StatusParams { - val old_state = params.get("old_state") ?: "pending"; + val old_state = params.get("old_state") ?: "pending" if (!names.contains(old_state)) { throw badRequest("Param 'old_state' must be one of $names_fmt", TalerErrorCode.GENERIC_PARAMETER_MALFORMED) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt @@ -20,13 +20,10 @@ package tech.libeufin.bank import io.ktor.http.* import io.ktor.server.application.* -import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import java.util.* -import tech.libeufin.common.* -import tech.libeufin.bank.auth.* -import tech.libeufin.bank.db.* +import tech.libeufin.bank.auth.auth +import tech.libeufin.bank.db.Database fun Routing.revenueApi(db: Database, ctx: BankConfig) { auth(db, TokenScope.readonly) { @@ -38,8 +35,8 @@ fun Routing.revenueApi(db: Database, ctx: BankConfig) { get("/accounts/{USERNAME}/taler-revenue/history") { val params = HistoryParams.extract(context.request.queryParameters) val bankAccount = call.bankInfo(db, ctx.payto) - val items = db.transaction.revenueHistory(params, bankAccount.bankAccountId, ctx.payto); - + val items = db.transaction.revenueHistory(params, bankAccount.bankAccountId, ctx.payto) + if (items.isEmpty()) { call.respond(HttpStatusCode.NoContent) } else { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt @@ -19,20 +19,25 @@ package tech.libeufin.bank -import tech.libeufin.common.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* -import java.net.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.longOrNull +import tech.libeufin.common.Base32Crockford +import tech.libeufin.common.EncodingException +import tech.libeufin.common.TalerAmount +import java.net.URL import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit -import java.util.* import java.util.concurrent.TimeUnit -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.* /** 32-byte Crockford's Base32 encoded data */ @Serializable(with = Base32Crockford32B.Serializer::class) @@ -141,20 +146,20 @@ class Base32Crockford64B { } /** 32-byte hash code */ -typealias ShortHashCode = Base32Crockford32B; +typealias ShortHashCode = Base32Crockford32B /** 64-byte hash code */ -typealias HashCode = Base32Crockford64B; +typealias HashCode = Base32Crockford64B /** * EdDSA and ECDHE public keys always point on Curve25519 * and represented using the standard 256 bits Ed25519 compact format, * converted to Crockford Base32. */ -typealias EddsaPublicKey = Base32Crockford32B; +typealias EddsaPublicKey = Base32Crockford32B /** Timestamp containing the number of seconds since epoch */ @Serializable data class TalerProtocolTimestamp( - @Serializable(with = TalerProtocolTimestamp.Serializer::class) + @Serializable(with = Serializer::class) val t_s: Instant, ) { companion object { @@ -186,7 +191,7 @@ data class TalerProtocolTimestamp( ?: throw badRequest("Could not convert t_s '${maybeTs.content}' to a number") when { ts < 0 -> throw badRequest("Negative timestamp not allowed") - ts > Instant.MAX.getEpochSecond() -> throw badRequest("Timestamp $ts too big to be represented in Kotlin") + ts > Instant.MAX.epochSecond -> throw badRequest("Timestamp $ts too big to be represented in Kotlin") else -> return Instant.ofEpochSecond(ts) } } @@ -205,7 +210,7 @@ class DecimalNumber { this.frac = frac } constructor(encoded: String) { - val match = PATTERN.matchEntire(encoded) ?: throw badRequest("Invalid decimal number format"); + val match = PATTERN.matchEntire(encoded) ?: throw badRequest("Invalid decimal number format") val (value, frac) = match.destructured this.value = value.toLongOrNull() ?: throw badRequest("Invalid value") if (this.value > TalerAmount.MAX_VALUE) throw badRequest("Value specified in decimal number is too large") @@ -249,7 +254,7 @@ class DecimalNumber { } companion object { - private val PATTERN = Regex("([0-9]+)(?:\\.([0-9]{1,8}))?"); + private val PATTERN = Regex("([0-9]+)(?:\\.([0-9]{1,8}))?") } } @@ -260,7 +265,7 @@ class DecimalNumber { */ @Serializable() data class RelativeTime( - @Serializable(with = RelativeTime.Serializer::class) + @Serializable(with = Serializer::class) val d_us: Duration ) { internal object Serializer : KSerializer<Duration> { @@ -292,7 +297,7 @@ data class RelativeTime( } companion object { - private const val MAX_SAFE_INTEGER = 9007199254740991L; // 2^53 - 1 + private const val MAX_SAFE_INTEGER = 9007199254740991L // 2^53 - 1 } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -19,18 +19,17 @@ package tech.libeufin.bank -import tech.libeufin.common.* -import io.ktor.http.* -import io.ktor.server.application.* -import kotlinx.serialization.* -import java.time.Duration -import java.time.Instant -import java.time.temporal.ChronoUnit -import java.util.* +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import tech.libeufin.common.IbanPayto +import tech.libeufin.common.Payto +import tech.libeufin.common.TalerAmount +import java.time.Instant /** * Allowed lengths for fractional digits in amounts. @@ -92,18 +91,18 @@ sealed class Option<out T> { fun get(): T? { return when (this) { - Option.None -> null - is Option.Some -> this.value + None -> null + is Some -> this.value } } inline fun some(lambda: (T) -> Unit) { - if (this is Option.Some) { + if (this is Some) { lambda(value) } } - fun isSome(): Boolean = this is Option.Some + fun isSome(): Boolean = this is Some @OptIn(ExperimentalSerializationApi::class) internal class Serializer<T> ( @@ -113,13 +112,13 @@ sealed class Option<out T> { override fun serialize(encoder: Encoder, value: Option<T>) { when (value) { - Option.None -> encoder.encodeNull() - is Option.Some -> valueSerializer.serialize(encoder, value.value) + None -> encoder.encodeNull() + is Some -> valueSerializer.serialize(encoder, value.value) } } override fun deserialize(decoder: Decoder): Option<T> { - return Option.Some(valueSerializer.deserialize(decoder)) + return Some(valueSerializer.deserialize(decoder)) } } } @@ -156,10 +155,10 @@ data class ChallengeContactData( val phone: Option<String?> = Option.None ) { init { - if (email.get()?.let { !EMAIL_PATTERN.matches(it) } ?: false) + if (email.get()?.let { !EMAIL_PATTERN.matches(it) } == true) throw badRequest("email contact data '$email' is malformed") - if (phone.get()?.let { !PHONE_PATTERN.matches(it) } ?: false) + if (phone.get()?.let { !PHONE_PATTERN.matches(it) } == true) throw badRequest("phone contact data '$phone' is malformed") } companion object { @@ -215,10 +214,10 @@ data class TokenRequest( @Serializable sealed interface MonitorResponse { - abstract val talerInCount: Long - abstract val talerInVolume: TalerAmount - abstract val talerOutCount: Long - abstract val talerOutVolume: TalerAmount + val talerInCount: Long + val talerInVolume: TalerAmount + val talerOutCount: Long + val talerOutVolume: TalerAmount } @Serializable diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Tan.kt b/bank/src/main/kotlin/tech/libeufin/bank/Tan.kt @@ -18,29 +18,27 @@ */ package tech.libeufin.bank -import java.security.SecureRandom -import java.time.Instant -import java.time.Duration -import java.text.DecimalFormat -import kotlinx.serialization.json.Json import io.ktor.http.* +import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* -import io.ktor.server.application.* -import tech.libeufin.bank.db.TanDAO.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.auth.* -import io.ktor.util.pipeline.PipelineContext +import kotlinx.serialization.json.Json +import tech.libeufin.bank.auth.username +import tech.libeufin.bank.db.Database +import tech.libeufin.bank.db.TanDAO.Challenge +import java.security.SecureRandom +import java.text.DecimalFormat +import java.time.Instant -inline suspend fun <reified B> ApplicationCall.respondChallenge( +suspend inline fun <reified B> ApplicationCall.respondChallenge( db: Database, op: Operation, body: B, channel: TanChannel? = null, info: String? = null ) { - val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), body); + val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), body) val code = Tan.genCode() val id = db.tan.new( login = username, @@ -59,7 +57,7 @@ inline suspend fun <reified B> ApplicationCall.respondChallenge( ) } -inline suspend fun <reified B> ApplicationCall.receiveChallenge( +suspend inline fun <reified B> ApplicationCall.receiveChallenge( db: Database, op: Operation ): Pair<B, Challenge?> { @@ -85,7 +83,7 @@ suspend fun ApplicationCall.challenge( } object Tan { - private val CODE_FORMAT = DecimalFormat("00000000"); + private val CODE_FORMAT = DecimalFormat("00000000") private val SECURE_RNG = SecureRandom() fun genCode(): String { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt @@ -26,12 +26,17 @@ import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.util.pipeline.PipelineContext +import io.ktor.util.pipeline.* +import tech.libeufin.bank.auth.auth +import tech.libeufin.bank.auth.authAdmin +import tech.libeufin.bank.auth.username +import tech.libeufin.bank.db.Database +import tech.libeufin.bank.db.ExchangeDAO +import tech.libeufin.bank.db.ExchangeDAO.AddIncomingResult +import tech.libeufin.bank.db.ExchangeDAO.TransferResult +import tech.libeufin.common.BankPaytoCtx +import tech.libeufin.common.TalerErrorCode import java.time.Instant -import tech.libeufin.common.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.ExchangeDAO.* -import tech.libeufin.bank.auth.* fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) { @@ -94,8 +99,8 @@ fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) { TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE ) - val items = db.exchange.dbLambda(params, bankAccount.bankAccountId, ctx.payto); - + val items = db.exchange.dbLambda(params, bankAccount.bankAccountId, ctx.payto) + if (items.isEmpty()) { call.respond(HttpStatusCode.NoContent) } else { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt b/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt @@ -21,20 +21,20 @@ package tech.libeufin.bank.auth import io.ktor.http.* import io.ktor.server.application.* -import io.ktor.server.routing.Route -import io.ktor.server.response.header -import io.ktor.util.AttributeKey -import io.ktor.util.pipeline.PipelineContext -import java.time.Instant -import tech.libeufin.bank.db.AccountDAO.* -import tech.libeufin.bank.db.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.* +import io.ktor.util.pipeline.* import tech.libeufin.bank.* +import tech.libeufin.bank.db.Database import tech.libeufin.common.* +import java.time.Instant /** Used to store if the currenly authenticated user is admin */ -private val AUTH_IS_ADMIN = AttributeKey<Boolean>("is_admin"); +private val AUTH_IS_ADMIN = AttributeKey<Boolean>("is_admin") + /** Used to store used auth token */ -private val AUTH_TOKEN = AttributeKey<ByteArray>("auth_token"); +private val AUTH_TOKEN = AttributeKey<ByteArray>("auth_token") /** Get username of the request account */ val ApplicationCall.username: String get() = expectParameter("USERNAME") @@ -89,7 +89,7 @@ fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, req if (requireAdmin && authLogin != "admin") { throw unauthorized("Only administrator allowed") } else { - val hasRight = authLogin == username || (allowAdmin && authLogin == "admin"); + val hasRight = authLogin == username || (allowAdmin && authLogin == "admin") if (!hasRight) { throw unauthorized("Customer $authLogin have no right on $username account") } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -19,10 +19,9 @@ package tech.libeufin.bank.db -import tech.libeufin.common.* -import java.time.* -import java.sql.Types import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.time.Instant /** Data access logic for accounts */ class AccountDAO(private val db: Database) { @@ -51,7 +50,7 @@ class AccountDAO(private val db: Database) { // Whether to check [internalPaytoUri] for idempotency checkPaytoIdempotent: Boolean ): AccountCreationResult = db.serializable { it -> - val now = Instant.now().toDbMicros() ?: throw faultyTimestampByBank(); + val now = Instant.now().toDbMicros() ?: throw faultyTimestampByBank() it.transaction { conn -> val idempotent = conn.prepareStatement(""" SELECT password_hash, name=? diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt @@ -19,11 +19,9 @@ package tech.libeufin.bank.db -import java.time.Duration -import java.time.Instant -import java.util.concurrent.TimeUnit -import tech.libeufin.common.* import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.time.Instant /** Data access logic for cashout operations */ class CashoutDAO(private val db: Database) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt @@ -19,9 +19,14 @@ package tech.libeufin.bank.db -import tech.libeufin.common.* -import tech.libeufin.bank.* -import tech.libeufin.bank.* +import tech.libeufin.bank.ConversionRate +import tech.libeufin.bank.DecimalNumber +import tech.libeufin.bank.RoundingMode +import tech.libeufin.bank.internalServerError +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.getAmount +import tech.libeufin.common.oneOrNull +import tech.libeufin.common.transaction /** Data access logic for conversion */ class ConversionDAO(private val db: Database) { @@ -68,8 +73,8 @@ class ConversionDAO(private val db: Database) { it.transaction { conn -> val check = conn.prepareStatement("select exists(select 1 from config where key='cashin_ratio')").oneOrNull { it.getBoolean(1) }!! if (!check) return@transaction null - val amount = conn.prepareStatement("SELECT (amount).val as amount_val, (amount).frac as amount_frac FROM config_get_amount(?) as amount"); - val roundingMode = conn.prepareStatement("SELECT config_get_rounding_mode(?)"); + val amount = conn.prepareStatement("SELECT (amount).val as amount_val, (amount).frac as amount_frac FROM config_get_amount(?) as amount") + val roundingMode = conn.prepareStatement("SELECT config_get_rounding_mode(?)") fun getAmount(name: String, currency: String): TalerAmount { amount.setString(1, name) return amount.oneOrNull { it.getAmount("amount", currency) }!! diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt @@ -19,21 +19,19 @@ package tech.libeufin.bank.db -import org.postgresql.jdbc.PgConnection -import org.postgresql.ds.PGSimpleDataSource -import org.postgresql.util.PSQLState +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.sql.* -import java.time.* -import java.util.* -import java.util.concurrent.TimeUnit -import kotlin.math.abs -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.* -import tech.libeufin.common.* -import io.ktor.http.HttpStatusCode import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.Types +import kotlin.math.abs private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-db") @@ -95,7 +93,7 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val if (params.which != null) { stmt.setInt(2, params.which) } else { - stmt.setNull(2, java.sql.Types.INTEGER) + stmt.setNull(2, Types.INTEGER) } stmt.oneOrNull { fiatCurrency?.run { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt @@ -19,12 +19,12 @@ package tech.libeufin.bank.db -import java.util.UUID -import java.time.Instant -import java.time.Duration -import java.util.concurrent.TimeUnit -import tech.libeufin.common.* import tech.libeufin.bank.* +import tech.libeufin.common.BankPaytoCtx +import tech.libeufin.common.getAmount +import tech.libeufin.common.getBankPayto +import tech.libeufin.common.toDbMicros +import java.time.Instant /** Data access logic for exchange specific logic */ class ExchangeDAO(private val db: Database) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt @@ -19,15 +19,15 @@ package tech.libeufin.bank.db -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.postgresql.ds.PGSimpleDataSource import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.common.* import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.util.* +import java.util.concurrent.ConcurrentHashMap private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-db-watcher") @@ -115,18 +115,18 @@ internal class NotificationWatcher(private val pgSource: PGSimpleDataSource) { private suspend fun <R, K, V> listen(map: ConcurrentHashMap<K, CountedSharedFlow<V>>, key: K, lambda: suspend (Flow<V>) -> R): R { // Register listener, create a new flow if missing val flow = map.compute(key) { _, v -> - val tmp = v ?: CountedSharedFlow(MutableSharedFlow(), 0); - tmp.count++; + val tmp = v ?: CountedSharedFlow(MutableSharedFlow(), 0) + tmp.count++ tmp - }!!.flow; + }!!.flow try { return lambda(flow) } finally { // Unregister listener, removing unused flow map.compute(key) { _, v -> - v!!; - v.count--; + v!! + v.count-- if (v.count > 0) v else null } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt @@ -19,12 +19,14 @@ package tech.libeufin.bank.db -import tech.libeufin.common.* -import tech.libeufin.bank.* -import tech.libeufin.bank.db.* -import java.util.concurrent.TimeUnit +import tech.libeufin.bank.Operation +import tech.libeufin.bank.TanChannel +import tech.libeufin.bank.internalServerError +import tech.libeufin.common.oneOrNull +import tech.libeufin.common.toDbMicros import java.time.Duration import java.time.Instant +import java.util.concurrent.TimeUnit /** Data access logic for tan challenged */ class TanDAO(private val db: Database) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt @@ -19,9 +19,13 @@ package tech.libeufin.bank.db -import tech.libeufin.common.* +import tech.libeufin.bank.BearerToken +import tech.libeufin.bank.TokenScope +import tech.libeufin.common.executeUpdateViolation +import tech.libeufin.common.microsToJavaInstant +import tech.libeufin.common.oneOrNull +import tech.libeufin.common.toDbMicros import java.time.Instant -import tech.libeufin.bank.* /** Data access logic for auth tokens */ class TokenDAO(private val db: Database) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt @@ -21,10 +21,9 @@ package tech.libeufin.bank.db import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.common.* -import java.time.* -import java.sql.Types import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.time.Instant private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-tx-dao") @@ -50,7 +49,7 @@ class TransactionDAO(private val db: Database) { timestamp: Instant, is2fa: Boolean ): BankTransactionResult = db.serializable { conn -> - val now = timestamp.toDbMicros() ?: throw faultyTimestampByBank(); + val now = timestamp.toDbMicros() ?: throw faultyTimestampByBank() conn.transaction { val stmt = conn.prepareStatement(""" SELECT diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt @@ -19,14 +19,14 @@ package tech.libeufin.bank.db -import java.util.UUID -import java.time.Instant -import java.time.Duration -import java.util.concurrent.TimeUnit -import tech.libeufin.common.* -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.* +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import tech.libeufin.bank.* +import tech.libeufin.common.* +import java.time.Instant +import java.util.* /** Data access logic for withdrawal operations */ class WithdrawalDAO(private val db: Database) { @@ -213,7 +213,7 @@ class WithdrawalDAO(private val db: Database) { // Initial loading val init = load() // Long polling if there is no operation or its not confirmed - if (init?.run { status(this) == params.old_state } ?: true) { + if (init?.run { status(this) == params.old_state } != false) { polling.join() load() } else { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -23,23 +23,21 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.* import io.ktor.server.request.* -import io.ktor.server.routing.Route -import io.ktor.server.routing.RouteSelector -import io.ktor.server.routing.RouteSelectorEvaluation -import io.ktor.server.routing.RoutingResolveContext +import io.ktor.server.routing.* import io.ktor.server.util.* -import io.ktor.util.pipeline.PipelineContext -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import java.net.* -import java.time.* -import java.time.temporal.* -import java.util.* +import io.ktor.util.pipeline.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import tech.libeufin.bank.auth.username +import tech.libeufin.bank.db.AccountDAO.AccountCreationResult +import tech.libeufin.bank.db.Database import tech.libeufin.common.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.AccountDAO.* -import tech.libeufin.bank.auth.* +import java.util.* fun ApplicationCall.expectParameter(name: String) = parameters[name] ?: throw badRequest( @@ -108,7 +106,7 @@ fun ApplicationCall.longParameter(name: String): Long { * It returns false in case of problems, true otherwise. */ suspend fun createAdminAccount(db: Database, cfg: BankConfig, pw: String? = null): AccountCreationResult { - var pwStr = pw; + var pwStr = pw if (pwStr == null) { val pwBuf = ByteArray(32) Random().nextBytes(pwBuf) diff --git a/bank/src/test/kotlin/AmountTest.kt b/bank/src/test/kotlin/AmountTest.kt @@ -17,16 +17,14 @@ * <http://www.gnu.org/licenses/> */ -import java.time.Instant -import java.util.* -import kotlin.test.* import org.junit.Test -import org.postgresql.jdbc.PgConnection -import tech.libeufin.bank.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.TransactionDAO.* -import tech.libeufin.bank.db.WithdrawalDAO.* +import tech.libeufin.bank.DecimalNumber +import tech.libeufin.bank.db.TransactionDAO.BankTransactionResult +import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalCreationResult import tech.libeufin.common.* +import java.time.Instant +import java.util.* +import kotlin.test.assertEquals class AmountTest { // Test amount computation in database diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt @@ -17,19 +17,18 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.server.testing.* -import java.util.* -import kotlin.test.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.bank.db.* -import tech.libeufin.common.* +import tech.libeufin.bank.BankAccountCreateWithdrawalResponse +import tech.libeufin.bank.BankWithdrawalOperationPostResponse +import tech.libeufin.bank.BankWithdrawalOperationStatus +import tech.libeufin.bank.WithdrawalStatus +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode +import tech.libeufin.common.json +import tech.libeufin.common.obj +import java.util.* +import kotlin.test.assertEquals class BankIntegrationApiTest { // GET /taler-integration/config diff --git a/bank/src/test/kotlin/ConversionApiTest.kt b/bank/src/test/kotlin/ConversionApiTest.kt @@ -17,18 +17,12 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.server.testing.* -import java.util.* -import kotlin.test.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* +import tech.libeufin.bank.ConversionResponse +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode +import kotlin.test.assertEquals class ConversionApiTest { // GET /conversion-info/config diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -17,23 +17,20 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.engine.* import io.ktor.server.testing.* -import java.time.Duration -import java.time.Instant -import java.util.* -import kotlin.test.* -import kotlinx.coroutines.* import kotlinx.serialization.json.JsonElement import org.junit.Test import tech.libeufin.bank.* -import tech.libeufin.bank.db.* import tech.libeufin.common.* +import java.time.Duration +import java.time.Instant +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull class CoreBankConfigTest { // GET /config @@ -592,7 +589,7 @@ class CoreBankAccountsApiTest { client.getA("/accounts/merchant").assertOkJson<AccountData> { obj -> assert(obj.is_public) } - }.assertNoContent(); + }.assertNoContent() client.getA("/accounts/merchant").assertOkJson<AccountData> { obj -> assert(!obj.is_public) } @@ -945,7 +942,7 @@ class CoreBankTransactionsApiTest { assertBalance("exchange", "+KUDOS:0") tx("merchant", "KUDOS:1", "exchange", "") // Bounce common to transaction tx("merchant", "KUDOS:1", "exchange", "Malformed") // Bounce malformed transaction - val reservePub = randEddsaPublicKey(); + val reservePub = randEddsaPublicKey() tx("merchant", "KUDOS:1", "exchange", randIncomingSubject(reservePub)) // Accept incoming tx("merchant", "KUDOS:1", "exchange", randIncomingSubject(reservePub)) // Bounce reserve_pub reuse assertBalance("merchant", "-KUDOS:1") diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt @@ -17,20 +17,18 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.junit.Test +import tech.libeufin.bank.createAdminAccount +import tech.libeufin.bank.db.AccountDAO.AccountCreationResult +import tech.libeufin.common.oneOrNull import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit import java.util.concurrent.TimeUnit -import kotlin.test.* -import kotlinx.coroutines.* -import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.bank.db.AccountDAO.* -import tech.libeufin.common.* +import kotlin.test.assertEquals +import kotlin.test.assertNull class DatabaseTest { diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt @@ -17,15 +17,17 @@ * <http://www.gnu.org/licenses/> */ -import java.time.Duration -import java.time.Instant -import java.time.temporal.ChronoUnit import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* +import tech.libeufin.bank.CreditDebitInfo +import tech.libeufin.bank.RelativeTime +import tech.libeufin.bank.TalerProtocolTimestamp +import tech.libeufin.common.TalerAmount +import java.time.Duration +import java.time.Instant +import java.time.temporal.ChronoUnit @Serializable data class MyJsonType( diff --git a/bank/src/test/kotlin/PaytoTest.kt b/bank/src/test/kotlin/PaytoTest.kt @@ -17,18 +17,15 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.server.testing.* -import java.util.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* -import kotlin.test.* +import tech.libeufin.bank.BankAccountTransactionInfo +import tech.libeufin.bank.RegisterAccountResponse +import tech.libeufin.bank.TransactionCreateResponse +import tech.libeufin.common.IbanPayto +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.json +import kotlin.test.assertEquals class PaytoTest { // x-taler-bank diff --git a/bank/src/test/kotlin/RevenueApiTest.kt b/bank/src/test/kotlin/RevenueApiTest.kt @@ -17,16 +17,9 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* -import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.server.testing.* -import java.util.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* import org.junit.Test -import tech.libeufin.bank.* +import tech.libeufin.bank.RevenueIncomingHistory class RevenueApiTest { // GET /accounts/{USERNAME}/taler-revenue/config diff --git a/bank/src/test/kotlin/SecurityTest.kt b/bank/src/test/kotlin/SecurityTest.kt @@ -17,22 +17,17 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.engine.* -import io.ktor.server.testing.* -import kotlin.test.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* +import tech.libeufin.common.TalerErrorCode +import tech.libeufin.common.deflate +import tech.libeufin.common.json +import tech.libeufin.common.obj inline fun <reified B> HttpRequestBuilder.jsonDeflate(b: B) { - val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b); + val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b) contentType(ContentType.Application.Json) headers.set(HttpHeaders.ContentEncoding, "deflate") setBody(json.toByteArray().inputStream().deflate().readBytes()) diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt @@ -18,17 +18,17 @@ */ import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.testing.* -import java.time.* -import java.time.Instant -import java.util.* -import kotlin.test.* import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* +import tech.libeufin.bank.MonitorResponse +import tech.libeufin.bank.MonitorWithConversion +import tech.libeufin.bank.Timeframe +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.executeQueryCheck +import tech.libeufin.common.oneOrNull +import tech.libeufin.common.toDbMicros +import java.time.Instant +import java.time.LocalDateTime +import kotlin.test.assertEquals class StatsTest { @Test @@ -46,7 +46,7 @@ class StatsTest { stmt.setLong(3, amount.value) stmt.setInt(4, amount.frac) stmt.setString(5, "") - stmt.executeQueryCheck(); + stmt.executeQueryCheck() } } diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt @@ -17,17 +17,13 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.plugins.* -import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.server.testing.* -import java.util.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* import org.junit.Test -import tech.libeufin.bank.* -import tech.libeufin.common.* +import tech.libeufin.bank.IncomingHistory +import tech.libeufin.bank.OutgoingHistory +import tech.libeufin.common.TalerErrorCode +import tech.libeufin.common.json +import tech.libeufin.common.obj class WireGatewayApiTest { // GET /accounts/{USERNAME}/taler-wire-gateway/config @@ -47,7 +43,7 @@ class WireGatewayApiTest { "exchange_base_url" to "http://exchange.example.com/" "wtid" to randShortHashCode() "credit_account" to merchantPayto.canonical - }; + } authRoutine(HttpMethod.Post, "/accounts/merchant/taler-wire-gateway/transfer", valid_req) @@ -208,7 +204,7 @@ class WireGatewayApiTest { "amount" to "KUDOS:44" "reserve_pub" to randEddsaPublicKey() "debit_account" to merchantPayto.canonical - }; + } authRoutine(HttpMethod.Post, "/accounts/merchant/taler-wire-gateway/admin/add-incoming", valid_req, requireAdmin = true) diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -17,22 +17,23 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.HttpClient +import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* -import java.io.ByteArrayOutputStream -import java.nio.file.* -import kotlin.test.* -import kotlin.io.path.* -import kotlin.random.Random -import kotlinx.coroutines.* -import kotlinx.serialization.json.* +import kotlinx.coroutines.runBlocking import tech.libeufin.bank.* -import tech.libeufin.bank.db.* -import tech.libeufin.bank.db.AccountDAO.* +import tech.libeufin.bank.db.AccountDAO.AccountCreationResult +import tech.libeufin.bank.db.Database import tech.libeufin.common.* +import java.nio.file.NoSuchFileException +import kotlin.io.path.Path +import kotlin.io.path.deleteExisting +import kotlin.io.path.readText +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertNotNull /* ----- Setup ----- */ @@ -156,7 +157,7 @@ fun bankSetup( } fun dbSetup(lambda: suspend (Database) -> Unit) { - setup() { db, _ -> lambda(db) } + setup { db, _ -> lambda(db) } } /* ----- Common actions ----- */ @@ -174,7 +175,7 @@ suspend fun ApplicationTestBuilder.assertBalance(account: String, amount: String client.get("/accounts/$account") { pwAuth("admin") }.assertOkJson<AccountData> { - val balance = it.balance; + val balance = it.balance val fmt = "${if (balance.credit_debit_indicator == CreditDebitInfo.debit) '-' else '+'}${balance.amount}" assertEquals(amount, fmt, "For $account") } @@ -305,7 +306,7 @@ suspend fun tanCode(info: String): String? { val file = Path("/tmp/tan-$info.txt") val code = file.readText() file.deleteExisting() - return code; + return code } catch (e: Exception) { if (e is NoSuchFileException) return null throw e @@ -391,7 +392,7 @@ fun assertException(msg: String, lambda: () -> Unit) { } } -inline suspend fun <reified B> HttpResponse.assertHistoryIds(size: Int, ids: (B) -> List<Long>): B { +suspend inline fun <reified B> HttpResponse.assertHistoryIds(size: Int, ids: (B) -> List<Long>): B { assertOk() val body = json<B>() val history = ids(body) @@ -418,14 +419,14 @@ inline suspend fun <reified B> HttpResponse.assertHistoryIds(size: Int, ids: (B) /* ----- Body helper ----- */ -inline suspend fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { +suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { assertOk() val body = json<B>() lambda(body) return body } -inline suspend fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B { +suspend inline fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Unit = {}): B { assertAccepted() val body = json<B>() lambda(body) @@ -435,7 +436,7 @@ inline suspend fun <reified B> HttpResponse.assertAcceptedJson(lambda: (B) -> Un /* ----- Auth ----- */ /** Auto auth get request */ -inline suspend fun HttpClient.getA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { +suspend inline fun HttpClient.getA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { return get(url) { pwAuth() builder(this) @@ -443,7 +444,7 @@ inline suspend fun HttpClient.getA(url: String, builder: HttpRequestBuilder.() - } /** Auto auth post request */ -inline suspend fun HttpClient.postA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { +suspend inline fun HttpClient.postA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { return post(url) { pwAuth() builder(this) @@ -451,7 +452,7 @@ inline suspend fun HttpClient.postA(url: String, builder: HttpRequestBuilder.() } /** Auto auth patch request */ -inline suspend fun HttpClient.patchA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { +suspend inline fun HttpClient.patchA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { return patch(url) { pwAuth() builder(this) @@ -459,7 +460,7 @@ inline suspend fun HttpClient.patchA(url: String, builder: HttpRequestBuilder.() } /** Auto auth delete request */ -inline suspend fun HttpClient.deleteA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { +suspend inline fun HttpClient.deleteA(url: String, builder: HttpRequestBuilder.() -> Unit = {}): HttpResponse { return delete(url) { pwAuth() builder(this) @@ -483,7 +484,7 @@ fun HttpRequestBuilder.pwAuth(username: String? = null) { fun randBytes(lenght: Int): ByteArray { val bytes = ByteArray(lenght) - kotlin.random.Random.nextBytes(bytes) + Random.nextBytes(bytes) return bytes } diff --git a/bank/src/test/kotlin/routines.kt b/bank/src/test/kotlin/routines.kt @@ -17,15 +17,20 @@ * <http://www.gnu.org/licenses/> */ -import tech.libeufin.bank.* -import tech.libeufin.common.* -import io.ktor.client.statement.HttpResponse -import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.client.request.* +import io.ktor.client.statement.* import io.ktor.http.* -import kotlin.test.* -import kotlinx.coroutines.* -import kotlinx.serialization.json.* +import io.ktor.server.testing.* +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonObject +import tech.libeufin.bank.BankAccountCreateWithdrawalResponse +import tech.libeufin.bank.WithdrawalStatus +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.TalerErrorCode +import tech.libeufin.common.json +import kotlin.test.assertEquals // Test endpoint is correctly authenticated suspend fun ApplicationTestBuilder.authRoutine( @@ -80,7 +85,7 @@ suspend fun ApplicationTestBuilder.authRoutine( } } -inline suspend fun <reified B> ApplicationTestBuilder.historyRoutine( +suspend inline fun <reified B> ApplicationTestBuilder.historyRoutine( url: String, crossinline ids: (B) -> List<Long>, registered: List<suspend () -> Unit>, @@ -201,7 +206,7 @@ inline suspend fun <reified B> ApplicationTestBuilder.historyRoutine( history("delta=-10&start=${id-4}").assertHistory(10) } -inline suspend fun <reified B> ApplicationTestBuilder.statusRoutine( +suspend inline fun <reified B> ApplicationTestBuilder.statusRoutine( url: String, crossinline status: (B) -> WithdrawalStatus ) { diff --git a/build.gradle b/build.gradle @@ -1,7 +1,5 @@ // This file is in the public domain. -import org.apache.tools.ant.filters.ReplaceTokens - plugins { id("org.jetbrains.kotlin.jvm") version "1.9.22" id("org.jetbrains.dokka") version "1.9.10" diff --git a/common/src/main/kotlin/Backoff.kt b/common/src/main/kotlin/Backoff.kt @@ -29,13 +29,13 @@ class ExpoBackoffDecorr( ) { private var sleep: Long = base - public fun next() : Long { + fun next() : Long { sleep = Random.nextDouble(base.toDouble(), sleep.toDouble() * factor) .toLong().coerceAtMost(max) return sleep } - public fun reset() { + fun reset() { sleep = base } } \ No newline at end of file diff --git a/common/src/main/kotlin/Cli.kt b/common/src/main/kotlin/Cli.kt @@ -19,21 +19,26 @@ package tech.libeufin.common -import com.github.ajalt.clikt.core.* -import com.github.ajalt.clikt.parameters.types.* -import com.github.ajalt.clikt.parameters.arguments.* -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.ProgramResult +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.groups.OptionGroup +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.enum +import com.github.ajalt.clikt.parameters.types.path import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level -import java.nio.file.Path private val logger: Logger = LoggerFactory.getLogger("libeufin-config") fun Throwable.fmtLog(logger: Logger) { var msg = StringBuilder(message ?: this::class.simpleName) - var cause = cause; + var cause = cause while (cause != null) { msg.append(": ") msg.append(cause.message ?: cause::class.simpleName) @@ -45,8 +50,8 @@ fun Throwable.fmtLog(logger: Logger) { fun cliCmd(logger: Logger, level: Level, lambda: () -> Unit) { // Set root log level - val root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as ch.qos.logback.classic.Logger - root.setLevel(ch.qos.logback.classic.Level.convertAnSLF4JLevel(level)); + val root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as ch.qos.logback.classic.Logger + root.level = ch.qos.logback.classic.Level.convertAnSLF4JLevel(level) // Run cli command catching all errors try { lambda() diff --git a/common/src/main/kotlin/Client.kt b/common/src/main/kotlin/Client.kt @@ -19,11 +19,12 @@ package tech.libeufin.common -import io.ktor.http.* -import kotlinx.serialization.json.* import io.ktor.client.request.* import io.ktor.client.statement.* -import java.io.ByteArrayOutputStream +import io.ktor.http.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject import kotlin.test.assertEquals /* ----- Json DSL ----- */ @@ -37,8 +38,8 @@ inline fun obj(from: JsonObject = JsonObject(emptyMap()), builderAction: JsonBui class JsonBuilder(from: JsonObject) { val content: MutableMap<String, JsonElement> = from.toMutableMap() - infix inline fun <reified T> String.to(v: T) { - val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v); + inline infix fun <reified T> String.to(v: T) { + val json = Json.encodeToJsonElement(kotlinx.serialization.serializer<T>(), v) content[this] = json } } @@ -46,7 +47,7 @@ class JsonBuilder(from: JsonObject) { /* ----- Json body helper ----- */ inline fun <reified B> HttpRequestBuilder.json(b: B) { - val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b); + val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b) contentType(ContentType.Application.Json) setBody(json) } @@ -58,10 +59,10 @@ inline fun HttpRequestBuilder.json( json(obj(from, builderAction)) } -inline suspend fun <reified B> HttpResponse.json(): B = +suspend inline fun <reified B> HttpResponse.json(): B = Json.decodeFromString(kotlinx.serialization.serializer<B>(), bodyAsText()) -inline suspend fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { +suspend inline fun <reified B> HttpResponse.assertOkJson(lambda: (B) -> Unit = {}): B { assertEquals(HttpStatusCode.OK, status) val body = json<B>() lambda(body) diff --git a/common/src/main/kotlin/Constants.kt b/common/src/main/kotlin/Constants.kt @@ -20,4 +20,4 @@ package tech.libeufin.common // DB const val MIN_VERSION: Int = 14 -const val SERIALIZATION_RETRY: Int = 10; -\ No newline at end of file +const val SERIALIZATION_RETRY: Int = 10 +\ No newline at end of file diff --git a/common/src/main/kotlin/CryptoUtil.kt b/common/src/main/kotlin/CryptoUtil.kt @@ -177,7 +177,7 @@ object CryptoUtil { } fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray { - return CryptoUtil.decryptEbicsE002( + return decryptEbicsE002( enc.encryptedTransactionKey, enc.encryptedData.inputStream(), privateKey @@ -289,10 +289,7 @@ object CryptoUtil { } catch (e: Exception) { return false } - if (data.size != 32) { - return false - } - return true + return data.size == 32 } fun hashStringSHA256(input: String): ByteArray { @@ -303,7 +300,7 @@ object CryptoUtil { val saltBytes = ByteArray(8) SecureRandom().nextBytes(saltBytes) val salt = bytesToBase64(saltBytes) - val pwh = bytesToBase64(CryptoUtil.hashStringSHA256("$salt|$pw")) + val pwh = bytesToBase64(hashStringSHA256("$salt|$pw")) return "sha256-salted\$$salt\$$pwh" } @@ -313,14 +310,14 @@ object CryptoUtil { "sha256" -> { // Support legacy unsalted passwords if (components.size != 2) throw Exception("bad password hash") val hash = components[1] - val pwh = bytesToBase64(CryptoUtil.hashStringSHA256(pw)) + val pwh = bytesToBase64(hashStringSHA256(pw)) return pwh == hash } "sha256-salted" -> { if (components.size != 3) throw Exception("bad password hash") val salt = components[1] val hash = components[2] - val pwh = bytesToBase64(CryptoUtil.hashStringSHA256("$salt|$pw")) + val pwh = bytesToBase64(hashStringSHA256("$salt|$pw")) return pwh == hash } else -> throw Exception("unsupported hash algo: '$algo'") diff --git a/common/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt @@ -19,19 +19,23 @@ package tech.libeufin.common +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.postgresql.ds.PGSimpleDataSource import org.postgresql.jdbc.PgConnection import org.postgresql.util.PSQLState import org.slf4j.Logger import org.slf4j.LoggerFactory -import kotlin.io.path.* -import java.nio.file.* import java.net.URI +import java.nio.file.Path import java.sql.PreparedStatement import java.sql.ResultSet import java.sql.SQLException -import kotlinx.coroutines.* -import com.zaxxer.hikari.* +import kotlin.io.path.Path +import kotlin.io.path.exists +import kotlin.io.path.readText fun getCurrentUser(): String = System.getProperty("user.name") @@ -122,15 +126,15 @@ fun PGSimpleDataSource.pgConnection(): PgConnection { fun <R> PgConnection.transaction(lambda: (PgConnection) -> R): R { try { - setAutoCommit(false); + autoCommit = false val result = lambda(this) - commit(); - setAutoCommit(true); + commit() + autoCommit = true return result - } catch(e: Exception){ - rollback(); - setAutoCommit(true); - throw e; + } catch (e: Exception) { + rollback() + autoCommit = true + throw e } } @@ -178,13 +182,13 @@ fun PreparedStatement.executeUpdateViolation(): Boolean { } fun PreparedStatement.executeProcedureViolation(): Boolean { - val savepoint = connection.setSavepoint(); + val savepoint = connection.setSavepoint() return try { executeUpdate() connection.releaseSavepoint(savepoint) true } catch (e: SQLException) { - connection.rollback(savepoint); + connection.rollback(savepoint) if (e.sqlState == PSQLState.UNIQUE_VIOLATION.state) return false throw e // rethrowing, not to hide other types of errors. } @@ -192,16 +196,16 @@ fun PreparedStatement.executeProcedureViolation(): Boolean { // TODO comment fun PgConnection.dynamicUpdate( - table: String, - fields: Sequence<String>, + table: String, + fields: Sequence<String>, filter: String, - bind: Sequence<Any?>, + bind: Sequence<Any?>, ) { val sql = fields.joinToString() if (sql.isEmpty()) return prepareStatement("UPDATE $table SET $sql $filter").run { for ((idx, value) in bind.withIndex()) { - setObject(idx+1, value) + setObject(idx + 1, value) } executeUpdate() } @@ -238,7 +242,7 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePre val patchName = "$sqlFilePrefix-$numStr" checkStmt.setString(1, patchName) - val patchCount = checkStmt.oneOrNull { it.getInt(1) } ?: throw Exception("unable to query patches"); + val patchCount = checkStmt.oneOrNull { it.getInt(1) } ?: throw Exception("unable to query patches") if (patchCount >= 1) { logger.info("patch $patchName already applied") continue @@ -266,10 +270,12 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePre // sqlFilePrefix is, for example, "libeufin-bank" or "libeufin-nexus" (no trailing dash). fun resetDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: String) { logger.info("reset DB, sqldir ${cfg.sqlDir}") - val isInitialized = conn.prepareStatement(""" + val isInitialized = conn.prepareStatement( + """ SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name='_v') AND EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name='${sqlFilePrefix.replace("-", "_")}') - """).oneOrNull { + """ + ).oneOrNull { it.getBoolean(1) }!! if (!isInitialized) { @@ -281,20 +287,20 @@ fun resetDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: conn.execSQLUpdate(sqlDrop) } -abstract class DbPool(cfg: String, schema: String): java.io.Closeable { +abstract class DbPool(cfg: String, schema: String) : java.io.Closeable { val pgSource = pgDataSource(cfg) private val pool: HikariDataSource init { - val config = HikariConfig(); + val config = HikariConfig() config.dataSource = pgSource config.schema = schema config.transactionIsolation = "TRANSACTION_SERIALIZABLE" pool = HikariDataSource(config) - pool.getConnection().use { con -> - val meta = con.getMetaData(); - val majorVersion = meta.getDatabaseMajorVersion() - val minorVersion = meta.getDatabaseMinorVersion() + pool.connection.use { con -> + val meta = con.metaData + val majorVersion = meta.databaseMajorVersion + val minorVersion = meta.databaseMinorVersion if (majorVersion < MIN_VERSION) { throw Exception("postgres version must be at least $MIN_VERSION.0 got $majorVersion.$minorVersion") } @@ -304,15 +310,14 @@ abstract class DbPool(cfg: String, schema: String): java.io.Closeable { suspend fun <R> conn(lambda: suspend (PgConnection) -> R): R { // Use a coroutine dispatcher that we can block as JDBC API is blocking return withContext(Dispatchers.IO) { - val conn = pool.getConnection() - conn.use{ it -> lambda(it.unwrap(PgConnection::class.java)) } + pool.connection.use { lambda(it.unwrap(PgConnection::class.java)) } } } suspend fun <R> serializable(lambda: suspend (PgConnection) -> R): R = conn { conn -> repeat(SERIALIZATION_RETRY) { try { - return@conn lambda(conn); + return@conn lambda(conn) } catch (e: SQLException) { if (e.sqlState != PSQLState.SERIALIZATION_FAILURE.state) throw e @@ -320,7 +325,7 @@ abstract class DbPool(cfg: String, schema: String): java.io.Closeable { } try { return@conn lambda(conn) - } catch(e: SQLException) { + } catch (e: SQLException) { logger.warn("Serialization failure after $SERIALIZATION_RETRY retry") throw e } @@ -331,7 +336,7 @@ abstract class DbPool(cfg: String, schema: String): java.io.Closeable { } } -fun ResultSet.getAmount(name: String, currency: String): TalerAmount{ +fun ResultSet.getAmount(name: String, currency: String): TalerAmount { return TalerAmount( getLong("${name}_val"), getInt("${name}_frac"), @@ -340,5 +345,5 @@ fun ResultSet.getAmount(name: String, currency: String): TalerAmount{ } fun ResultSet.getBankPayto(payto: String, name: String, ctx: BankPaytoCtx): String { - return Payto.parse(getString(payto)).bank(getString(name), ctx) + return Payto.parse(getString(payto)).bank(getString(name), ctx) } \ No newline at end of file diff --git a/common/src/main/kotlin/Stream.kt b/common/src/main/kotlin/Stream.kt @@ -19,22 +19,22 @@ package tech.libeufin.common -import java.io.InputStream import java.io.FilterInputStream +import java.io.InputStream +import java.util.* import java.util.zip.DeflaterInputStream import java.util.zip.InflaterInputStream -import java.util.zip.* -import java.util.Base64 +import java.util.zip.ZipInputStream /** Unzip an input stream and run [lambda] over each entry */ fun InputStream.unzipEach(lambda: (String, InputStream) -> Unit) { ZipInputStream(this).use { zip -> while (true) { val entry = zip.getNextEntry() - if (entry == null) break; + if (entry == null) break val entryStream = object: FilterInputStream(zip) { override fun close() { - zip.closeEntry(); + zip.closeEntry() } } lambda(entry.name, entryStream) diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt @@ -19,11 +19,14 @@ package tech.libeufin.common -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.* import io.ktor.http.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import java.net.URI sealed class CommonError(msg: String): Exception(msg) { @@ -45,7 +48,7 @@ class TalerAmount { } constructor(encoded: String) { val match = PATTERN.matchEntire(encoded) ?: - throw CommonError.AmountFormat("Invalid amount format"); + throw CommonError.AmountFormat("Invalid amount format") val (currency, value, frac) = match.destructured this.currency = currency this.value = value.toLongOrNull() ?: @@ -97,8 +100,8 @@ class TalerAmount { companion object { const val FRACTION_BASE = 100000000 - const val MAX_VALUE = 4503599627370496L; // 2^52 - private val PATTERN = Regex("([A-Z]{1,11}):([0-9]+)(?:\\.([0-9]{1,8}))?"); + const val MAX_VALUE = 4503599627370496L // 2^52 + private val PATTERN = Regex("([A-Z]{1,11}):([0-9]+)(?:\\.([0-9]{1,8}))?") } } @@ -107,7 +110,7 @@ value class IBAN private constructor(val value: String) { override fun toString(): String = value companion object { - private val SEPARATOR = Regex("[\\ \\-]"); + private val SEPARATOR = Regex("[\\ \\-]") fun parse(raw: String): IBAN { val iban: String = raw.uppercase().replace(SEPARATOR, "") @@ -120,7 +123,7 @@ value class IBAN private constructor(val value: String) { } } val str = builder.toString() - val mod = str.toBigInteger().mod(97.toBigInteger()).toInt(); + val mod = str.toBigInteger().mod(97.toBigInteger()).toInt() if (mod != 1) throw CommonError.Payto("Iban malformed, modulo is $mod expected 1") return IBAN(iban) } @@ -179,7 +182,7 @@ sealed class Payto { } override fun deserialize(decoder: Decoder): Payto { - return Payto.parse(decoder.decodeString()) + return parse(decoder.decodeString()) } } @@ -192,7 +195,7 @@ sealed class Payto { } if (parsed.scheme != "payto") throw CommonError.Payto("expect a payto URI got '${parsed.scheme}'") - val params = (parsed.query ?: "").parseUrlEncodedParameters(); + val params = (parsed.query ?: "").parseUrlEncodedParameters() val amount = params["amount"]?.run { TalerAmount(this) } val message = params["message"] val receiverName = params["receiver-name"] @@ -264,13 +267,13 @@ class IbanPayto internal constructor( } override fun deserialize(decoder: Decoder): IbanPayto { - return Payto.parse(decoder.decodeString()).expectIban() + return parse(decoder.decodeString()).expectIban() } } companion object { fun rand(): IbanPayto { - return Payto.parse("payto://iban/SANDBOXX/${IBAN.rand()}").expectIban() + return parse("payto://iban/SANDBOXX/${IBAN.rand()}").expectIban() } } } @@ -287,7 +290,7 @@ class XTalerBankPayto internal constructor( companion object { fun forUsername(username: String): XTalerBankPayto { - return Payto.parse("payto://x-taler-bank/hostname/$username").expectXTalerBank() + return parse("payto://x-taler-bank/hostname/$username").expectXTalerBank() } } } diff --git a/common/src/main/kotlin/TalerConfig.kt b/common/src/main/kotlin/TalerConfig.kt @@ -21,8 +21,10 @@ package tech.libeufin.common import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.nio.file.AccessDeniedException +import java.nio.file.NoSuchFileException +import java.nio.file.Path import kotlin.io.path.* -import java.nio.file.* private val logger: Logger = LoggerFactory.getLogger("libeufin-config") @@ -298,14 +300,14 @@ class TalerConfig internal constructor( if (s[l] != '$') { // normal character result.append(s[l]) - l++; + l++ continue } if (l + 1 < s.length && s[l + 1] == '{') { // ${var} var depth = 1 val start = l - var p = start + 2; + var p = start + 2 var hasDefault = false var insideNamePath = true // Find end of the ${...} expression diff --git a/common/src/main/kotlin/strings.kt b/common/src/main/kotlin/strings.kt @@ -83,7 +83,7 @@ fun getQueryParam(uriQueryString: String, param: String): String? { } fun String.splitOnce(pat: String): Pair<String, String>? { - val split = split(pat, limit=2); + val split = split(pat, limit=2) if (split.size != 2) return null return Pair(split[0], split[1]) } \ No newline at end of file diff --git a/common/src/main/kotlin/time.kt b/common/src/main/kotlin/time.kt @@ -21,8 +21,7 @@ package tech.libeufin.common import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.time.* -import java.time.format.DateTimeFormatter +import java.time.Instant import java.time.temporal.ChronoUnit private val logger: Logger = LoggerFactory.getLogger("libeufin-common") diff --git a/ebics/codegen.py b/ebics/codegen.py @@ -1,9 +1,10 @@ # Update EBICS constants file using latest external code sets files +import polars as pl import requests -from zipfile import ZipFile from io import BytesIO -import polars as pl +from zipfile import ZipFile + def iso20022codegen(): # Get XLSX zip file from server diff --git a/ebics/src/main/kotlin/Ebics.kt b/ebics/src/main/kotlin/Ebics.kt @@ -24,23 +24,25 @@ package tech.libeufin.ebics +import io.ktor.http.* +import org.w3c.dom.Document import tech.libeufin.common.CryptoUtil -import io.ktor.http.HttpStatusCode -import tech.libeufin.ebics.ebics_h004.* +import tech.libeufin.ebics.ebics_h004.EbicsRequest +import tech.libeufin.ebics.ebics_h004.EbicsResponse +import tech.libeufin.ebics.ebics_h004.EbicsTypes +import tech.libeufin.ebics.ebics_h004.HPBResponseOrderData import tech.libeufin.ebics.ebics_h005.Ebics3Response import tech.libeufin.ebics.ebics_s001.UserSignatureData +import java.io.InputStream import java.security.SecureRandom import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime -import java.util.* -import java.io.InputStream import javax.xml.bind.JAXBElement import javax.xml.datatype.DatatypeFactory import javax.xml.datatype.XMLGregorianCalendar -import org.w3c.dom.Document data class EbicsProtocolError( val httpStatusCode: HttpStatusCode, @@ -271,7 +273,7 @@ enum class EbicsReturnCode(val errorCode: String) { companion object { fun lookup(errorCode: String): EbicsReturnCode { - for (x in values()) { + for (x in entries) { if (x.errorCode == errorCode) { return x } diff --git a/ebics/src/main/kotlin/EbicsOrderUtil.kt b/ebics/src/main/kotlin/EbicsOrderUtil.kt @@ -19,10 +19,10 @@ package tech.libeufin.ebics -import java.lang.IllegalArgumentException +import tech.libeufin.common.deflate +import tech.libeufin.common.inflate +import tech.libeufin.common.toHexString import java.security.SecureRandom -import java.io.InputStream -import tech.libeufin.common.* /** * Helpers for dealing with order compression, encryption, decryption, chunking and re-assembly. @@ -40,7 +40,7 @@ object EbicsOrderUtil { return bytes.inputStream().deflate().readAllBytes() } - @kotlin.ExperimentalStdlibApi + @ExperimentalStdlibApi fun generateTransactionId(): String { val rng = SecureRandom() val res = ByteArray(16) diff --git a/ebics/src/main/kotlin/XMLUtil.kt b/ebics/src/main/kotlin/XMLUtil.kt @@ -264,7 +264,7 @@ class XMLUtil private constructor() { logger.warn("Validation failed: ${e}") return false } - return true; + return true } /** @@ -361,7 +361,7 @@ class XMLUtil private constructor() { /* Make Transformer. */ val tf = TransformerFactory.newInstance() val t = tf.newTransformer() - t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") /* Make string writer. */ val sw = StringWriter() /* Extract string. */ @@ -396,7 +396,7 @@ class XMLUtil private constructor() { fun signEbicsResponse(ebicsResponse: EbicsResponse, privateKey: RSAPrivateCrtKey): ByteArray { val doc = convertJaxbToDocument(ebicsResponse) signEbicsDocument(doc, privateKey) - val signedDoc = XMLUtil.convertDomToBytes(doc) + val signedDoc = convertDomToBytes(doc) // logger.debug("response: $signedDoc") return signedDoc } diff --git a/ebics/src/main/kotlin/XmlCombinators.kt b/ebics/src/main/kotlin/XmlCombinators.kt @@ -19,15 +19,14 @@ package tech.libeufin.ebics -import com.sun.xml.txw2.output.IndentingXMLStreamWriter -import org.w3c.dom.Document import org.w3c.dom.Element +import java.io.InputStream import java.io.StringWriter +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import javax.xml.stream.XMLOutputFactory import javax.xml.stream.XMLStreamWriter -import java.io.InputStream -import java.time.format.* -import java.time.* class XmlBuilder(private val w: XMLStreamWriter) { fun el(path: String, lambda: XmlBuilder.() -> Unit = {}) { @@ -35,7 +34,7 @@ class XmlBuilder(private val w: XMLStreamWriter) { w.writeStartElement(it) } lambda() - path.splitToSequence('/').forEach { + path.splitToSequence('/').forEach { w.writeEndElement() } } @@ -129,7 +128,7 @@ class XmlDestructor internal constructor(private val el: Element) { fun bool(): Boolean = el.textContent.toBoolean() fun date(): LocalDate = LocalDate.parse(text(), DateTimeFormatter.ISO_DATE) fun dateTime(): LocalDateTime = LocalDateTime.parse(text(), DateTimeFormatter.ISO_DATE_TIME) - inline fun <reified T : kotlin.Enum<T>> enum(): T = java.lang.Enum.valueOf(T::class.java, text()) + inline fun <reified T : Enum<T>> enum(): T = java.lang.Enum.valueOf(T::class.java, text()) fun attr(index: String): String = el.getAttribute(index) } diff --git a/ebics/src/main/kotlin/ebics_h004/EbicsResponse.kt b/ebics/src/main/kotlin/ebics_h004/EbicsResponse.kt @@ -1,9 +1,7 @@ package tech.libeufin.ebics.ebics_h004 import org.apache.xml.security.binding.xmldsig.SignatureType -import org.apache.xml.security.binding.xmldsig.SignedInfoType import tech.libeufin.common.CryptoUtil -import tech.libeufin.ebics.XMLUtil import java.math.BigInteger import javax.xml.bind.annotation.* import javax.xml.bind.annotation.adapters.CollapsedStringAdapter @@ -133,18 +131,18 @@ class EbicsResponse { val resp = EbicsResponse().apply { this.version = "H004" this.revision = 1 - this.header = EbicsResponse.Header().apply { + this.header = Header().apply { this.authenticate = true - this.mutable = EbicsResponse.MutableHeaderType().apply { + this.mutable = MutableHeaderType().apply { this.reportText = errorText this.returnCode = errorCode this.transactionPhase = phase } - _static = EbicsResponse.StaticHeaderType() + _static = StaticHeaderType() } this.authSignature = SignatureType() - this.body = EbicsResponse.Body().apply { - this.returnCode = EbicsResponse.ReturnCode().apply { + this.body = Body().apply { + this.returnCode = ReturnCode().apply { this.authenticate = true this.value = errorCode } diff --git a/ebics/src/main/kotlin/ebics_h004/EbicsTypes.kt b/ebics/src/main/kotlin/ebics_h004/EbicsTypes.kt @@ -259,7 +259,7 @@ object EbicsTypes { @XmlAccessorType(XmlAccessType.NONE) class UserIDType { @get:XmlValue - lateinit var value: String; + lateinit var value: String @get:XmlAttribute(name = "Status") var status: Int? = null diff --git a/ebics/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt b/ebics/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt @@ -204,7 +204,7 @@ class EbicsUnsecuredRequest { signaturePubKeyInfo = SignatureTypes.SignaturePubKeyInfoType().apply { signatureVersion = "A006" pubKeyValue = SignatureTypes.PubKeyValueType().apply { - rsaKeyValue = org.apache.xml.security.binding.xmldsig.RSAKeyValueType().apply { + rsaKeyValue = RSAKeyValueType().apply { exponent = signKey.publicExponent.toByteArray() modulus = signKey.modulus.toByteArray() } diff --git a/ebics/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt b/ebics/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt @@ -1,6 +1,5 @@ package tech.libeufin.ebics.ebics_h004 -import java.security.Permission import javax.xml.bind.annotation.* @XmlAccessorType(XmlAccessType.NONE) diff --git a/ebics/src/main/kotlin/ebics_h004/package-info.java b/ebics/src/main/kotlin/ebics_h004/package-info.java @@ -8,5 +8,6 @@ elementFormDefault = XmlNsForm.QUALIFIED ) package tech.libeufin.ebics.ebics_h004; -import javax.xml.bind.annotation.XmlSchema; -import javax.xml.bind.annotation.XmlNsForm; -\ No newline at end of file + +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema; +\ No newline at end of file diff --git a/ebics/src/main/kotlin/ebics_h005/Ebics3Response.kt b/ebics/src/main/kotlin/ebics_h005/Ebics3Response.kt @@ -1,9 +1,7 @@ package tech.libeufin.ebics.ebics_h005 import org.apache.xml.security.binding.xmldsig.SignatureType -import org.apache.xml.security.binding.xmldsig.SignedInfoType import tech.libeufin.common.CryptoUtil -import tech.libeufin.ebics.XMLUtil import tech.libeufin.ebics.ebics_h004.EbicsTypes import java.math.BigInteger import javax.xml.bind.annotation.* @@ -133,18 +131,18 @@ class Ebics3Response { val resp = Ebics3Response().apply { this.version = "H005" this.revision = 1 - this.header = Ebics3Response.Header().apply { + this.header = Header().apply { this.authenticate = true - this.mutable = Ebics3Response.MutableHeaderType().apply { + this.mutable = MutableHeaderType().apply { this.reportText = errorText this.returnCode = errorCode this.transactionPhase = phase } - _static = Ebics3Response.StaticHeaderType() + _static = StaticHeaderType() } this.authSignature = SignatureType() - this.body = Ebics3Response.Body().apply { - this.returnCode = Ebics3Response.ReturnCode().apply { + this.body = Body().apply { + this.returnCode = ReturnCode().apply { this.authenticate = true this.value = errorCode } diff --git a/ebics/src/main/kotlin/ebics_h005/Ebics3Types.kt b/ebics/src/main/kotlin/ebics_h005/Ebics3Types.kt @@ -258,7 +258,7 @@ object Ebics3Types { @XmlAccessorType(XmlAccessType.NONE) class UserIDType { @get:XmlValue - lateinit var value: String; + lateinit var value: String @get:XmlAttribute(name = "Status") var status: Int? = null diff --git a/ebics/src/main/kotlin/ebics_h005/package-info.java b/ebics/src/main/kotlin/ebics_h005/package-info.java @@ -8,6 +8,6 @@ elementFormDefault = XmlNsForm.QUALIFIED ) package tech.libeufin.ebics.ebics_h005; -import javax.xml.bind.annotation.XmlNs; + import javax.xml.bind.annotation.XmlNsForm; import javax.xml.bind.annotation.XmlSchema; \ No newline at end of file diff --git a/ebics/src/test/kotlin/EbicsMessagesTest.kt b/ebics/src/test/kotlin/EbicsMessagesTest.kt @@ -21,12 +21,12 @@ import junit.framework.TestCase.assertEquals import org.apache.xml.security.binding.xmldsig.SignatureType import org.junit.Test import org.w3c.dom.Element +import tech.libeufin.common.CryptoUtil +import tech.libeufin.ebics.XMLUtil import tech.libeufin.ebics.ebics_h004.* import tech.libeufin.ebics.ebics_hev.HEVResponse import tech.libeufin.ebics.ebics_hev.SystemReturnCodeType import tech.libeufin.ebics.ebics_s001.SignatureTypes -import tech.libeufin.common.CryptoUtil -import tech.libeufin.ebics.XMLUtil import javax.xml.datatype.DatatypeFactory import kotlin.test.assertNotNull import kotlin.test.assertTrue diff --git a/ebics/src/test/kotlin/EbicsOrderUtilTest.kt b/ebics/src/test/kotlin/EbicsOrderUtilTest.kt @@ -303,6 +303,6 @@ class EbicsOrderUtilTest { </UserInfo> </HTDResponseOrderData> """.trimIndent().toByteArray().inputStream() - XMLUtil.convertToJaxb<HTDResponseOrderData>(orderDataXml); + XMLUtil.convertToJaxb<HTDResponseOrderData>(orderDataXml) } } \ No newline at end of file diff --git a/ebics/src/test/kotlin/XmlCombinatorsTest.kt b/ebics/src/test/kotlin/XmlCombinatorsTest.kt @@ -20,7 +20,7 @@ import org.junit.Test import tech.libeufin.ebics.XmlBuilder import tech.libeufin.ebics.constructXml -import kotlin.test.* +import kotlin.test.assertEquals class XmlCombinatorsTest { diff --git a/ebics/src/test/kotlin/XmlUtilTest.kt b/ebics/src/test/kotlin/XmlUtilTest.kt @@ -18,20 +18,18 @@ */ import org.apache.xml.security.binding.xmldsig.SignatureType +import org.junit.Assert.assertTrue import org.junit.Test -import org.junit.Assert.* -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import tech.libeufin.common.CryptoUtil +import tech.libeufin.common.decodeBase64 +import tech.libeufin.ebics.XMLUtil +import tech.libeufin.ebics.XMLUtil.Companion.signEbicsResponse import tech.libeufin.ebics.ebics_h004.EbicsKeyManagementResponse import tech.libeufin.ebics.ebics_h004.EbicsResponse import tech.libeufin.ebics.ebics_h004.EbicsTypes import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData -import tech.libeufin.common.* -import tech.libeufin.ebics.XMLUtil import java.security.KeyPairGenerator -import java.util.* import javax.xml.transform.stream.StreamSource -import tech.libeufin.ebics.XMLUtil.Companion.signEbicsResponse class XmlUtilTest { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt @@ -18,16 +18,13 @@ */ package tech.libeufin.nexus -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.postgresql.jdbc.PgConnection import org.postgresql.util.PSQLState import tech.libeufin.common.* import java.sql.PreparedStatement import java.sql.SQLException -import java.time.Instant -import java.util.Date import java.text.SimpleDateFormat +import java.time.Instant +import java.util.* fun Instant.fmtDate(): String { val formatter = SimpleDateFormat("yyyy-MM-dd") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt @@ -19,8 +19,9 @@ package tech.libeufin.nexus import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option import tech.libeufin.common.* /** diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -19,25 +19,24 @@ package tech.libeufin.nexus import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.groups.* import com.github.ajalt.clikt.parameters.arguments.* +import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.types.* import io.ktor.client.* import io.ktor.client.plugins.* import kotlinx.coroutines.* -import tech.libeufin.nexus.ebics.* import tech.libeufin.common.* import tech.libeufin.ebics.* import tech.libeufin.ebics.ebics_h005.Ebics3Request +import tech.libeufin.nexus.ebics.* import java.io.IOException import java.io.InputStream import java.time.Instant import java.time.LocalDate import java.time.ZoneId -import java.util.UUID -import kotlin.io.path.* import kotlin.io.* +import kotlin.io.path.* /** * Necessary data to perform a download. @@ -355,7 +354,7 @@ private suspend fun fetchDocuments( ctx: FetchContext, docs: List<Document> ): Boolean { - val lastExecutionTime: Instant? = ctx.pinnedStart; + val lastExecutionTime: Instant? = ctx.pinnedStart return docs.all { doc -> try { if (lastExecutionTime == null) { @@ -394,26 +393,26 @@ enum class Document { ; fun shortDescription(): String = when (this) { - Document.acknowledgement -> "EBICS acknowledgement" - Document.status -> "Payment status" + acknowledgement -> "EBICS acknowledgement" + status -> "Payment status" //Document.report -> "Account intraday reports" - Document.notification -> "Debit & credit notifications" + notification -> "Debit & credit notifications" //Document.statement -> "Account statements" } fun fullDescription(): String = when (this) { - Document.acknowledgement -> "EBICS acknowledgement - CustomerAcknowledgement HAC pain.002" - Document.status -> "Payment status - CustomerPaymentStatusReport pain.002" + acknowledgement -> "EBICS acknowledgement - CustomerAcknowledgement HAC pain.002" + status -> "Payment status - CustomerPaymentStatusReport pain.002" //report -> "Account intraday reports - BankToCustomerAccountReport camt.052" - Document.notification -> "Debit & credit notifications - BankToCustomerDebitCreditNotification camt.054" + notification -> "Debit & credit notifications - BankToCustomerDebitCreditNotification camt.054" //statement -> "Account statements - BankToCustomerStatement camt.053" } fun doc(): SupportedDocument = when (this) { - Document.acknowledgement -> SupportedDocument.PAIN_002_LOGS - Document.status -> SupportedDocument.PAIN_002 + acknowledgement -> SupportedDocument.PAIN_002_LOGS + status -> SupportedDocument.PAIN_002 //Document.report -> SupportedDocument.CAMT_052 - Document.notification -> SupportedDocument.CAMT_054 + notification -> SupportedDocument.CAMT_054 //Document.statement -> SupportedDocument.CAMT_053 } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -20,19 +20,16 @@ package tech.libeufin.nexus import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.options.* import io.ktor.client.* import kotlinx.coroutines.runBlocking -import tech.libeufin.ebics.ebics_h004.EbicsTypes -import kotlinx.serialization.encodeToString -import tech.libeufin.nexus.ebics.* import tech.libeufin.common.* import tech.libeufin.ebics.* -import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData +import tech.libeufin.nexus.ebics.* +import java.nio.file.* import java.time.Instant import kotlin.io.path.* -import java.nio.file.* /** * Obtains the client private keys, regardless of them being @@ -90,9 +87,7 @@ private fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean { println("Encryption key: ${encHash.spaceEachTwo()}") println("Authentication key: ${authHash.spaceEachTwo()}") val userResponse: String? = readlnOrNull() - if (userResponse == "yes, accept") - return true - return false + return userResponse == "yes, accept" } /** diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt @@ -20,15 +20,15 @@ package tech.libeufin.nexus import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.options.* import io.ktor.client.* import kotlinx.coroutines.* +import tech.libeufin.common.* import tech.libeufin.nexus.ebics.EbicsSideError import tech.libeufin.nexus.ebics.EbicsSideException import tech.libeufin.nexus.ebics.EbicsUploadException import tech.libeufin.nexus.ebics.submitPain001 -import tech.libeufin.common.* import java.time.* import java.util.* diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -20,10 +20,10 @@ package tech.libeufin.nexus import tech.libeufin.common.* import tech.libeufin.ebics.* +import java.io.InputStream import java.net.URLEncoder import java.time.* import java.time.format.* -import java.io.InputStream /** @@ -151,7 +151,7 @@ fun parseCustomerAck(xml: InputStream): List<CustomerAck> { one("CstmrPmtStsRpt").map("OrgnlPmtInfAndSts") { val actionType = one("OrgnlPmtInfId").enum<HacAction>() one("StsRsnInf") { - var timestamp: Instant? = null; + var timestamp: Instant? = null var orderId: String? = null one("Orgtr").one("Id").one("OrgId").each("Othr") { val value = one("Id") @@ -331,7 +331,7 @@ private fun notificationForEachTx( destructXml(xml, "Document") { opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") { each("Ntry") { - if (opt("RvslInd")?.bool() ?: false) { + if (opt("RvslInd")?.bool() == true) { logger.warn("Skip reversal transaction") } else { one("Sts") { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt @@ -19,15 +19,22 @@ package tech.libeufin.nexus -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.serialization.Contextual +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encodeToString +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule +import tech.libeufin.common.Base32Crockford +import tech.libeufin.common.CryptoUtil +import java.nio.file.* import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey -import tech.libeufin.common.* -import java.nio.file.* import kotlin.io.path.* val JSON = Json { @@ -126,7 +133,7 @@ private inline fun <reified T> persistJsonFile(obj: T, path: Path, name: String) // Write to temp file then rename to enable atomicity when possible val tmp = Files.createTempFile(parent, "tmp_", "_${path.fileName}") tmp.writeText(content) - tmp.moveTo(path, StandardCopyOption.REPLACE_EXISTING); + tmp.moveTo(path, StandardCopyOption.REPLACE_EXISTING) } catch (e: Exception) { when { !parent.isWritable() -> throw Exception("Could not write $name at '$path': permission denied on '$parent'") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt @@ -22,9 +22,9 @@ package tech.libeufin.nexus import tech.libeufin.common.* import java.io.* import java.nio.file.* -import kotlin.io.path.* -import kotlin.io.* import java.time.* +import kotlin.io.* +import kotlin.io.path.* /** * Log EBICS files for debugging @@ -53,7 +53,7 @@ class FileLogger(path: String?) { * @param hac only true when downloading via HAC (EBICS 2) */ fun logFetch(stream: InputStream, hac: Boolean = false): InputStream { - if (dir == null) return stream; + if (dir == null) return stream val content = stream.readBytes() // Subdir based on current day. val now = Instant.now() @@ -81,7 +81,7 @@ class FileLogger(path: String?) { * @param content EBICS submit content */ fun logSubmit(content: String) { - if (dir == null) return; + if (dir == null) return // Subdir based on current day. val now = Instant.now() diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -27,16 +27,16 @@ package tech.libeufin.nexus import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.subcommands -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.groups.* import com.github.ajalt.clikt.parameters.arguments.* -import kotlinx.coroutines.* +import com.github.ajalt.clikt.parameters.groups.* +import com.github.ajalt.clikt.parameters.options.* import io.ktor.client.* import io.ktor.util.* +import kotlinx.coroutines.* import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.nexus.ebics.* import tech.libeufin.common.* +import tech.libeufin.nexus.ebics.* import java.nio.file.Path import java.time.Instant @@ -84,8 +84,7 @@ fun getFrequencyInSeconds(humanFormat: String): Int? { logger.error("Input was empty") return null } - val lastChar = trimmed.last() - val howManySeconds: Int = when (lastChar) { + val howManySeconds: Int = when (val lastChar = trimmed.last()) { 's' -> {1} 'm' -> {60} 'h' -> {60 * 60} diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -23,20 +23,21 @@ package tech.libeufin.nexus.ebics -import io.ktor.client.* import org.slf4j.Logger import org.slf4j.LoggerFactory +import tech.libeufin.ebics.* +import tech.libeufin.ebics.ebics_h004.EbicsKeyManagementResponse +import tech.libeufin.ebics.ebics_h004.EbicsNpkdRequest +import tech.libeufin.ebics.ebics_h004.EbicsRequest +import tech.libeufin.ebics.ebics_h004.EbicsUnsecuredRequest import tech.libeufin.nexus.BankPublicKeysFile import tech.libeufin.nexus.ClientPrivateKeysFile import tech.libeufin.nexus.EbicsSetupConfig -import tech.libeufin.ebics.* -import tech.libeufin.ebics.ebics_h004.* -import tech.libeufin.ebics.ebics_h005.Ebics3Request +import java.io.InputStream import java.security.interfaces.RSAPrivateCrtKey import java.time.Instant import java.time.ZoneId import java.util.* -import java.io.InputStream import javax.xml.datatype.DatatypeFactory private val logger: Logger = LoggerFactory.getLogger("libeufin-nexus-ebics2") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -19,15 +19,15 @@ package tech.libeufin.nexus.ebics import io.ktor.client.* -import tech.libeufin.nexus.BankPublicKeysFile -import tech.libeufin.nexus.ClientPrivateKeysFile -import tech.libeufin.nexus.EbicsSetupConfig -import tech.libeufin.nexus.logger import tech.libeufin.ebics.PreparedUploadData import tech.libeufin.ebics.XMLUtil import tech.libeufin.ebics.ebics_h005.Ebics3Request import tech.libeufin.ebics.getNonce import tech.libeufin.ebics.getXmlDate +import tech.libeufin.nexus.BankPublicKeysFile +import tech.libeufin.nexus.ClientPrivateKeysFile +import tech.libeufin.nexus.EbicsSetupConfig +import tech.libeufin.nexus.logger import java.math.BigInteger import java.time.Instant import java.util.* diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -43,15 +43,13 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.utils.io.jvm.javaio.* -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import tech.libeufin.nexus.* import tech.libeufin.common.* import tech.libeufin.ebics.* import tech.libeufin.ebics.ebics_h005.Ebics3Request -import java.io.SequenceInputStream +import tech.libeufin.nexus.* import java.io.ByteArrayOutputStream import java.io.InputStream +import java.io.SequenceInputStream import java.security.interfaces.RSAPrivateCrtKey import java.time.LocalDateTime import java.time.format.DateTimeFormatter diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt @@ -17,14 +17,18 @@ * <http://www.gnu.org/licenses/> */ +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.testing.test +import tech.libeufin.common.CryptoUtil import tech.libeufin.nexus.* -import com.github.ajalt.clikt.core.* -import com.github.ajalt.clikt.testing.* -import kotlin.test.* -import java.io.* -import java.nio.file.* -import kotlin.io.path.* -import tech.libeufin.common.* +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertEquals val nexusCmd = LibeufinNexusCommand() diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt @@ -20,14 +20,14 @@ import io.ktor.client.* import io.ktor.client.engine.mock.* import io.ktor.client.request.* -import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule -import kotlinx.coroutines.* +import kotlinx.coroutines.runBlocking +import tech.libeufin.common.TalerAmount +import tech.libeufin.common.fromFile +import tech.libeufin.common.initializeDatabaseTables +import tech.libeufin.common.resetDatabaseTables import tech.libeufin.nexus.* -import tech.libeufin.common.* -import java.security.interfaces.RSAPrivateCrtKey import java.time.Instant -import kotlin.io.path.* +import kotlin.io.path.Path fun conf( conf: String = "test.conf", diff --git a/nexus/src/test/kotlin/ConfigLoading.kt b/nexus/src/test/kotlin/ConfigLoading.kt @@ -18,11 +18,9 @@ */ import org.junit.Test -import tech.libeufin.nexus.EbicsSetupConfig -import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE import tech.libeufin.nexus.getFrequencyInSeconds -import kotlin.test.* -import tech.libeufin.common.* +import kotlin.test.assertEquals +import kotlin.test.assertNull class ConfigLoading { // Checks converting human-readable durations to seconds. diff --git a/nexus/src/test/kotlin/DatabaseTest.kt b/nexus/src/test/kotlin/DatabaseTest.kt @@ -17,15 +17,14 @@ * <http://www.gnu.org/licenses/> */ -import kotlinx.coroutines.runBlocking import org.junit.Test -import tech.libeufin.nexus.* -import tech.libeufin.common.* +import tech.libeufin.common.TalerAmount +import tech.libeufin.nexus.DatabaseSubmissionState +import tech.libeufin.nexus.InitiatedPayment +import tech.libeufin.nexus.PaymentInitiationOutcome import java.time.Instant import kotlin.random.Random import kotlin.test.* -import kotlin.test.assertEquals -import kotlin.io.path.* class OutgoingPaymentsTest { @Test diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt @@ -17,18 +17,15 @@ * <http://www.gnu.org/licenses/> */ -import io.ktor.client.* import io.ktor.client.engine.mock.* import io.ktor.http.* -import kotlinx.coroutines.runBlocking -import org.junit.Ignore import org.junit.Test -import tech.libeufin.nexus.* -import tech.libeufin.nexus.ebics.* import tech.libeufin.ebics.XMLUtil import tech.libeufin.ebics.ebics_h004.EbicsUnsecuredRequest -import kotlin.test.* -import kotlin.io.path.* +import tech.libeufin.nexus.ebics.* +import kotlin.io.path.Path +import kotlin.io.path.writeBytes +import kotlin.test.assertEquals class Ebics { // Checks XML is valid and INI. diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt @@ -18,10 +18,15 @@ */ import org.junit.Test -import tech.libeufin.nexus.* import tech.libeufin.common.CryptoUtil -import kotlin.io.path.* -import kotlin.test.* +import tech.libeufin.nexus.* +import kotlin.io.path.Path +import kotlin.io.path.deleteIfExists +import kotlin.io.path.notExists +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue class PublicKeys { diff --git a/nexus/src/test/kotlin/MySerializers.kt b/nexus/src/test/kotlin/MySerializers.kt @@ -17,15 +17,12 @@ * <http://www.gnu.org/licenses/> */ -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule import org.junit.Test +import tech.libeufin.common.Base32Crockford import tech.libeufin.common.CryptoUtil -import java.security.interfaces.RSAPrivateCrtKey +import tech.libeufin.nexus.ClientPrivateKeysFile +import tech.libeufin.nexus.JSON import kotlin.test.assertEquals -import tech.libeufin.common.* -import tech.libeufin.nexus.* class MySerializers { // Testing deserialization of RSA private keys. diff --git a/nexus/src/test/kotlin/Parsing.kt b/nexus/src/test/kotlin/Parsing.kt @@ -18,10 +18,14 @@ */ import org.junit.Test -import tech.libeufin.nexus.* -import tech.libeufin.common.* -import java.lang.StringBuilder -import kotlin.test.* +import tech.libeufin.common.TalerAmount +import tech.libeufin.nexus.getAmountNoCurrency +import tech.libeufin.nexus.isReservePub +import tech.libeufin.nexus.removeSubjectNoise +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertNull class Parsing { diff --git a/parsing-tests/checks.py b/parsing-tests/checks.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -import os -import sys import json -from subprocess import Popen, PIPE +import os from deepdiff import DeepDiff +from subprocess import Popen, PIPE + # return dict with parse-result. def call_parser(xml_file): diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt @@ -78,7 +78,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") { override fun run() { // List available platform - val platforms = Path("test").listDirectoryEntries().filter { it.isDirectory() }.map { it.getFileName().toString() } + val platforms = Path("test").listDirectoryEntries().filter { it.isDirectory() }.map { it.fileName.toString() } if (!platforms.contains(platform)) { println("Unknown platform '$platform', expected one of $platforms") throw ProgramResult(1) diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt @@ -96,7 +96,7 @@ inline fun assertException(msg: String, lambda: () -> Unit) { class IntegrationTest { val nexusCmd = LibeufinNexusCommand() - val bankCmd = LibeufinBankCommand(); + val bankCmd = LibeufinBankCommand() val client = HttpClient(CIO) @Test @@ -278,7 +278,7 @@ class IntegrationTest { // Cashin repeat(3) { i -> - val reservePub = randBytes(32); + val reservePub = randBytes(32) val amount = TalerAmount("EUR:${20+i}") val subject = "cashin test $i: ${Base32Crockford.encode(reservePub)}" nexusCmd.run("testing fake-incoming $flags --subject \"$subject\" --amount $amount $userPayTo") @@ -302,10 +302,10 @@ class IntegrationTest { // Cashout repeat(3) { i -> - val requestUid = randBytes(32); + val requestUid = randBytes(32) val amount = TalerAmount("KUDOS:${10+i}") val convert = client.get("http://0.0.0.0:8080/conversion-info/cashout-rate?amount_debit=$amount") - .assertOkJson<ConversionResponse>().amount_credit; + .assertOkJson<ConversionResponse>().amount_credit client.post("http://0.0.0.0:8080/accounts/customer/cashouts") { basicAuth("customer", "password") json {