commit 33e1a6ae220a5b359d26f6648b90a2c20545fdfb parent 7edfaa739f019e89e38e078cee08f33bfab0272b Author: Antoine A <> Date: Wed, 24 Jan 2024 13:09:41 +0100 Use java.nio and typed Path everywhere Diffstat:
22 files changed, 126 insertions(+), 176 deletions(-)
diff --git a/bank/conf/test_tan_err.conf b/bank/conf/test_tan_err.conf @@ -7,7 +7,6 @@ ALLOW_REGISTRATION = yes ALLOW_ACCOUNT_DELETION = yes ALLOW_EDIT_CASHOUT_PAYTO_URI = yes tan_sms = libeufin-tan-fail.sh -tan_email = [libeufin-bankdb-postgres] CONFIG = postgresql:///libeufincheck diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -22,6 +22,8 @@ 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.* /** * Application the parsed configuration. @@ -46,8 +48,8 @@ data class BankConfig( val allowConversion: Boolean, val fiatCurrency: String?, val fiatCurrencySpec: CurrencySpecification?, - val spaPath: String?, - val tanChannels: Map<TanChannel, String> + val spaPath: Path?, + val tanChannels: Map<TanChannel, Path> ) @Serializable @@ -101,7 +103,7 @@ fun TalerConfig.loadBankConfig(): BankConfig { } val tanChannels = buildMap { for (channel in TanChannel.entries) { - lookupPath("libeufin-bank", "tan_$channel")?.notEmptyOrNull()?.let { + lookupPath("libeufin-bank", "tan_$channel")?.let { put(channel, it) } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -630,7 +630,7 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) { val tanScript = ctx.tanChannels.get(res.tanChannel) ?: throw unsupportedTanChannel(res.tanChannel) val exitValue = withContext(Dispatchers.IO) { - val process = ProcessBuilder(tanScript, res.tanInfo).start() + val process = ProcessBuilder(tanScript.toString(), res.tanInfo).start() try { process.outputWriter().use { it.write(res.tanCode) } process.onExit().await() diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -44,7 +44,6 @@ import java.time.Duration import java.util.zip.DataFormatException import java.util.zip.Inflater import java.sql.SQLException -import java.io.File import kotlinx.coroutines.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* @@ -55,6 +54,7 @@ import org.postgresql.util.PSQLState import tech.libeufin.bank.db.AccountDAO.* import tech.libeufin.bank.db.* import tech.libeufin.common.* +import kotlin.io.path.* private val logger: Logger = LoggerFactory.getLogger("libeufin-bank") // Dirty local variable to stop the server in test TODO remove this ugly hack @@ -214,7 +214,7 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) { get("/") { call.respondRedirect("/webui/") } - staticFiles("/webui/", File(it)) + staticFiles("/webui/", it.toFile()) } } } @@ -272,14 +272,14 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve") throw Exception("Account is not an exchange: an exchange account named 'exchange' is required for conversion to be enabled") } logger.info("Ensure conversion is enabled") - val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql") + val sqlProcedures = Path("${dbCfg.sqlDir}/libeufin-conversion-setup.sql") if (!sqlProcedures.exists()) { throw Exception("Missing libeufin-conversion-setup.sql file") } db.conn { it.execSQLUpdate(sqlProcedures.readText()) } } else { logger.info("Ensure conversion is disabled") - val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-drop.sql") + val sqlProcedures = Path("${dbCfg.sqlDir}/libeufin-conversion-drop.sql") if (!sqlProcedures.exists()) { throw Exception("Missing libeufin-conversion-drop.sql file") } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt @@ -24,7 +24,6 @@ import org.postgresql.ds.PGSimpleDataSource import org.postgresql.util.PSQLState import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File import java.sql.* import java.time.* import java.util.* diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt @@ -23,9 +23,9 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* import java.io.ByteArrayOutputStream -import java.io.File import java.util.zip.DeflaterOutputStream import kotlin.test.* +import kotlin.io.path.* import kotlin.random.Random import kotlinx.coroutines.* import kotlinx.serialization.json.* @@ -66,7 +66,7 @@ fun setup( initializeDatabaseTables(conn, dbCfg, "libeufin-nexus") resetDatabaseTables(conn, dbCfg, "libeufin-bank") initializeDatabaseTables(conn, dbCfg, "libeufin-bank") - val sqlProcedures = File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql") + val sqlProcedures = Path("${dbCfg.sqlDir}/libeufin-conversion-setup.sql") conn.execSQLUpdate(sqlProcedures.readText()) } lambda(it, ctx) @@ -301,10 +301,11 @@ suspend fun ApplicationTestBuilder.convert(amount: String): TalerAmount { } suspend fun tanCode(info: String): String? { - val file = File("/tmp/tan-$info.txt"); + // TODO rewrite with only two files access + val file = Path("/tmp/tan-$info.txt") if (file.exists()) { val code = file.readText() - file.delete() + file.deleteExisting() return code; } else { return null diff --git a/common/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt @@ -24,7 +24,8 @@ import org.postgresql.jdbc.PgConnection import org.postgresql.util.PSQLState import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File +import kotlin.io.path.* +import java.nio.file.* import java.net.URI import java.sql.PreparedStatement import java.sql.ResultSet @@ -100,7 +101,7 @@ fun getJdbcConnectionFromPg(pgConn: String): String { data class DatabaseConfig( val dbConnStr: String, - val sqlDir: String + val sqlDir: Path ) fun pgDataSource(dbConfig: String): PGSimpleDataSource { @@ -219,7 +220,7 @@ fun maybeApplyV(conn: PgConnection, cfg: DatabaseConfig) { ) if (!checkVSchema.executeQueryCheck()) { logger.debug("_v schema not found, applying versioning.sql") - val sqlVersioning = File("${cfg.sqlDir}/versioning.sql").readText() + val sqlVersioning = Path("${cfg.sqlDir}/versioning.sql").readText() conn.execSQLUpdate(sqlVersioning) } } @@ -243,7 +244,7 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePre continue } - val path = File("${cfg.sqlDir}/$sqlFilePrefix-$numStr.sql") + val path = Path("${cfg.sqlDir}/$sqlFilePrefix-$numStr.sql") if (!path.exists()) { logger.info("path $path doesn't exist anymore, stopping") break @@ -252,7 +253,7 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePre val sqlPatchText = path.readText() conn.execSQLUpdate(sqlPatchText) } - val sqlProcedures = File("${cfg.sqlDir}/$sqlFilePrefix-procedures.sql") + val sqlProcedures = Path("${cfg.sqlDir}/$sqlFilePrefix-procedures.sql") if (!sqlProcedures.exists()) { logger.info("no procedures.sql for the SQL collection: $sqlFilePrefix") return@transaction @@ -276,7 +277,7 @@ fun resetDatabaseTables(conn: PgConnection, cfg: DatabaseConfig, sqlFilePrefix: return } - val sqlDrop = File("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText() + val sqlDrop = Path("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText() conn.execSQLUpdate(sqlDrop) } diff --git a/common/src/main/kotlin/TalerConfig.kt b/common/src/main/kotlin/TalerConfig.kt @@ -21,11 +21,8 @@ package tech.libeufin.common import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File -import java.nio.file.Paths -import kotlin.io.path.Path -import kotlin.io.path.isReadable -import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.* +import java.nio.file.* private val logger: Logger = LoggerFactory.getLogger("libeufin-config") @@ -82,7 +79,7 @@ class TalerConfig( private val installPathBinary = configSource.installPathBinary val sections: Set<String> get() = sectionMap.keys - private fun internalLoadFromString(s: String, sourceFilename: String?) { + private fun internalLoadFromString(s: String, source: Path?) { val lines = s.lines() var lineNum = 0 var currentSection: String? = null @@ -97,22 +94,20 @@ class TalerConfig( val directiveMatch = reDirective.matchEntire(line) if (directiveMatch != null) { - if (sourceFilename == null) { + if (source == null) { throw TalerConfigError("Directives are only supported when loading from file") } val directiveName = directiveMatch.groups[1]!!.value.lowercase() val directiveArg = directiveMatch.groups[2]!!.value when (directiveName) { "inline" -> { - val innerFilename = normalizeInlineFilename(sourceFilename, directiveArg.trim()) + val innerFilename = source.resolveSibling(directiveArg.trim()) this.loadFromFilename(innerFilename) } - "inline-matching" -> { val glob = directiveArg.trim() - this.loadFromGlob(sourceFilename, glob) + this.loadFromGlob(source, glob) } - "inline-secret" -> { val arg = directiveArg.trim() val sp = arg.split(" ") @@ -120,7 +115,7 @@ class TalerConfig( throw TalerConfigError("invalid configuration, @inline-secret@ directive requires exactly two arguments") } val sectionName = sp[0] - val secretFilename = normalizeInlineFilename(sourceFilename, sp[1]) + val secretFilename = source.resolveSibling(sp[1]) loadSecret(sectionName, secretFilename) } @@ -152,42 +147,19 @@ class TalerConfig( section.entries[optName] = optVal continue } - throw TalerConfigError("expected section header, option assignment or directive in line $lineNum file ${sourceFilename ?: "<input>"}") + throw TalerConfigError("expected section header, option assignment or directive in line $lineNum file ${source ?: "<input>"}") } } - private fun loadFromGlob(parentFilename: String, glob: String) { - val fullFileglob: String - val parentDir = Path(parentFilename).parent!!.toString() - if (glob.startsWith("/")) { - fullFileglob = glob - } else { - fullFileglob = Paths.get(parentDir, glob).toString() - } - - val head = Path(fullFileglob).parent.toString() - val tail = Path(fullFileglob).fileName.toString() - + private fun loadFromGlob(source: Path, glob: String) { // FIXME: Check that the Kotlin glob matches the glob from our spec - for (entry in Path(head).listDirectoryEntries(tail)) { - loadFromFilename(entry.toString()) - } - } - - private fun normalizeInlineFilename(parentFilename: String, f: String): String { - if (f[0] == '/') { - return f - } - val parentDirPath = Path(parentFilename).toRealPath().parent - if (parentDirPath == null) { - throw TalerConfigError("unable to normalize inline path, cannot resolve parent directory of $parentFilename") + for (entry in source.parent.listDirectoryEntries(glob)) { + loadFromFilename(entry) } - val parentDir = parentDirPath.toString() - return Paths.get(parentDir, f).toRealPath().toString() } - private fun loadSecret(sectionName: String, secretFilename: String) { - if (!Path(secretFilename).isReadable()) { + private fun loadSecret(sectionName: String, secretFilename: Path) { + if (!secretFilename.isReadable()) { logger.warn("unable to read secrets from $secretFilename") } else { this.loadFromFilename(secretFilename) @@ -245,15 +217,13 @@ class TalerConfig( * Read values into the configuration from the given entry point * filename. Defaults are *not* loaded automatically. */ - fun loadFromFilename(filename: String) { - val f = File(filename) - val contents = f.readText() - internalLoadFromString(contents, filename) + fun loadFromFilename(path: Path) { + internalLoadFromString(path.readText(), path) } - private fun loadDefaultsFromDir(dirname: String) { - for (filePath in Path(dirname).listDirectoryEntries()) { - loadFromFilename(filePath.toString()) + private fun loadDefaultsFromDir(dirname: Path) { + for (filePath in dirname.listDirectoryEntries()) { + loadFromFilename(filePath) } } @@ -263,26 +233,26 @@ class TalerConfig( */ fun loadDefaults() { val installDir = getInstallPath() - val baseConfigDir = Paths.get(installDir, "share/$projectName/config.d").toString() - setSystemDefault("PATHS", "PREFIX", "${installDir}/") - setSystemDefault("PATHS", "BINDIR", "${installDir}/bin/") - setSystemDefault("PATHS", "LIBEXECDIR", "${installDir}/$projectName/libexec/") - setSystemDefault("PATHS", "DOCDIR", "${installDir}/share/doc/$projectName/") - setSystemDefault("PATHS", "ICONDIR", "${installDir}/share/icons/") - setSystemDefault("PATHS", "LOCALEDIR", "${installDir}/share/locale/") - setSystemDefault("PATHS", "LIBDIR", "${installDir}/lib/$projectName/") - setSystemDefault("PATHS", "DATADIR", "${installDir}/share/$projectName/") + val baseConfigDir = Path(installDir, "share/$projectName/config.d") + setSystemDefault("PATHS", "PREFIX", "$installDir/") + setSystemDefault("PATHS", "BINDIR", "$installDir/bin/") + setSystemDefault("PATHS", "LIBEXECDIR", "$installDir/$projectName/libexec/") + setSystemDefault("PATHS", "DOCDIR", "$installDir/share/doc/$projectName/") + setSystemDefault("PATHS", "ICONDIR", "$installDir/share/icons/") + setSystemDefault("PATHS", "LOCALEDIR", "$installDir/share/locale/") + setSystemDefault("PATHS", "LIBDIR", "$installDir/lib/$projectName/") + setSystemDefault("PATHS", "DATADIR", "$installDir/share/$projectName/") loadDefaultsFromDir(baseConfigDir) } - private fun variableLookup(x: String, recursionDepth: Int = 0): String? { + private fun variableLookup(x: String, recursionDepth: Int = 0): Path? { val pathRes = this.lookupString("PATHS", x) if (pathRes != null) { return pathsub(pathRes, recursionDepth + 1) } val envVal = System.getenv(x) if (envVal != null) { - return envVal + return Path(envVal) } return null } @@ -294,7 +264,7 @@ class TalerConfig( * * This substitution is typically only done for paths. */ - fun pathsub(x: String, recursionDepth: Int = 0): String { + fun pathsub(x: String, recursionDepth: Int = 0): Path { if (recursionDepth > 128) { throw TalerConfigError("recursion limit in path substitution exceeded") } @@ -372,7 +342,7 @@ class TalerConfig( l = varEnd } } - return result.toString() + return Path(result.toString()) } /** @@ -383,7 +353,7 @@ class TalerConfig( fun load(entrypoint: String? = null) { loadDefaults() if (entrypoint != null) { - loadFromFilename(entrypoint) + loadFromFilename(Path(entrypoint)) } else { val defaultFilename = findDefaultConfigFilename() if (defaultFilename != null) { @@ -397,25 +367,25 @@ class TalerConfig( * * If no such file can be found, return null. */ - private fun findDefaultConfigFilename(): String? { + private fun findDefaultConfigFilename(): Path? { val xdg = System.getenv("XDG_CONFIG_HOME") val home = System.getenv("HOME") - var filename: String? = null + var filename: Path? = null if (xdg != null) { - filename = Paths.get(xdg, "$componentName.conf").toString() + filename = Path(xdg, "$componentName.conf") } else if (home != null) { - filename = Paths.get(home, ".config/$componentName.conf").toString() + filename = Path(home, ".config/$componentName.conf") } - if (filename != null && File(filename).exists()) { + if (filename != null && filename.exists()) { return filename } - val etc1 = "/etc/$componentName.conf" - if (File(etc1).exists()) { + val etc1 = Path("/etc/$componentName.conf") + if (etc1.exists()) { return etc1 } - val etc2 = "/etc/$projectName/$componentName.conf" - if (File(etc2).exists()) { + val etc2 = Path("/etc/$projectName/$componentName.conf") + if (etc2.exists()) { return etc2 } return null @@ -436,9 +406,9 @@ class TalerConfig( val pathEnv = System.getenv("PATH") val paths = pathEnv.split(":") for (p in paths) { - val possiblePath = Paths.get(p, name).toString() - if (File(possiblePath).exists()) { - return Paths.get(p, "..").toRealPath().toString() + val possiblePath = Path(p, name) + if (possiblePath.exists()) { + return Path(p, "..").toRealPath().toString() } } return "/usr" @@ -478,12 +448,12 @@ class TalerConfig( lookupBoolean(section, option) ?: throw TalerConfigError("expected boolean in configuration section $section option $option") - fun lookupPath(section: String, option: String): String? { + fun lookupPath(section: String, option: String): Path? { val entry = lookupString(section, option) ?: return null return pathsub(entry) } - fun requirePath(section: String, option: String): String = + fun requirePath(section: String, option: String): Path = lookupPath(section, option) ?: throw TalerConfigError("expected path for section $section option $option") } diff --git a/common/src/test/kotlin/CryptoUtilTest.kt b/common/src/test/kotlin/CryptoUtilTest.kt @@ -19,8 +19,8 @@ import org.junit.Ignore import org.junit.Test +import kotlin.io.path.* import tech.libeufin.common.* -import java.io.File import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateCrtKey import java.util.* @@ -179,7 +179,7 @@ class CryptoUtilTest { fun gnunetEncodeCheck() { val blob = ByteArray(30) Random().nextBytes(blob) - val b = File("/tmp/libeufin-blob.bin") + val b = Path("/tmp/libeufin-blob.bin") b.writeBytes(blob) val enc = Base32Crockford.encode(blob) // The following output needs to match the one from @@ -194,8 +194,8 @@ class CryptoUtilTest { @Ignore fun gnunetDecodeCheck() { // condition: "gnunet-base32 -d /tmp/blob.enc" needs to decode to /tmp/blob.bin - val blob = File("/tmp/blob.bin").readBytes() - val blobEnc = File("/tmp/blob.enc").readText(Charsets.UTF_8) + val blob = Path("/tmp/blob.bin").readBytes() + val blobEnc = Path("/tmp/blob.enc").readText(Charsets.UTF_8) val dec = Base32Crockford.decode(blobEnc) assertTrue(blob.contentEquals(dec)) } diff --git a/common/src/test/kotlin/TalerConfigTest.kt b/common/src/test/kotlin/TalerConfigTest.kt @@ -20,6 +20,7 @@ import org.junit.Test import kotlin.test.assertEquals import tech.libeufin.common.* +import kotlin.io.path.* class TalerConfigTest { @@ -54,10 +55,10 @@ class TalerConfigTest { conf.putValueString("foo", "bar2", "baz") assertEquals("baz", conf.lookupString("foo", "bar")) - assertEquals("baz", conf.lookupPath("foo", "bar")) + assertEquals(Path("baz"), conf.lookupPath("foo", "bar")) conf.putValueString("foo", "dir1", "foo/\$DATADIR/bar") - assertEquals("foo/mydir/bar", conf.lookupPath("foo", "dir1")) + assertEquals(Path("foo/mydir/bar"), conf.lookupPath("foo", "dir1")) } } diff --git a/contrib/bank.conf b/contrib/bank.conf @@ -28,10 +28,10 @@ CURRENCY = KUDOS # FIAT_CURRENCY = EUR # Path to TAN challenge transmission script via sms. If not specified, this TAN channel will not be supported. -# TAN_SMS = +# TAN_SMS = libeufin-tan-sms.sh # Path to TAN challenge transmission script via email. If not specified, this TAN channel will not be supported. -# TAN_EMAIL = +# TAN_EMAIL = libeufin-tan-email.sh # How "libeufin-bank serve" serves its API, this can either be tcp or unix SERVE = tcp diff --git a/debian/etc/libeufin/libeufin-bank.conf b/debian/etc/libeufin/libeufin-bank.conf @@ -21,10 +21,10 @@ CURRENCY = KUDOS # ALLOW_CONVERSION = no # Path to TAN challenge transmission script via sms. If not specified, this TAN channel wil be unuspported. -TAN_SMS = +# TAN_SMS = libeufin-tan-sms.sh # Path to TAN challenge transmission script via email. If not specified, this TAN channel wil be unuspported. -TAN_EMAIL = +# TAN_EMAIL = libeufin-tan-email.sh # Where "libeufin-bank serve" serves its API SERVE = tcp diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -26,9 +26,7 @@ import kotlinx.coroutines.* import tech.libeufin.nexus.ebics.* import tech.libeufin.common.* import tech.libeufin.ebics.ebics_h005.Ebics3Request -import java.io.File import java.io.IOException -import java.nio.file.Path import java.time.Instant import java.time.LocalDate import java.time.ZoneId diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -25,15 +25,14 @@ import com.github.ajalt.clikt.parameters.groups.* import io.ktor.client.* import kotlinx.coroutines.runBlocking import tech.libeufin.ebics.ebics_h004.EbicsTypes -import java.io.File 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 java.time.Instant -import java.nio.file.* import kotlin.io.path.* +import java.nio.file.* /** * Obtains the client private keys, regardless of them being @@ -43,7 +42,7 @@ import kotlin.io.path.* * @param path path to the file that contains the keys. * @return current or new client keys */ -private fun loadOrGenerateClientKeys(path: String): ClientPrivateKeysFile { +private fun loadOrGenerateClientKeys(path: Path): ClientPrivateKeysFile { // If exists load from disk val current = loadClientKeys(path) if (current != null) return current @@ -204,9 +203,10 @@ fun extractEbicsConfig(configFile: String?): EbicsSetupConfig { */ private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) { val pdf = generateKeysPdf(privs, cfg) - val pdfFile = File("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf") + // TODO rewrite with a single file access + val pdfFile = Path("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf") if (pdfFile.exists()) { - throw Exception("PDF file exists already at: ${pdfFile.path}, not overriding it") + throw Exception("PDF file exists already at: ${pdfFile}, not overriding it") } try { pdfFile.writeBytes(pdf) @@ -249,8 +249,8 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { // Eject PDF if the keys were submitted for the first time, or the user asked. if (keysNotSub || generateRegistrationPdf) makePdf(clientKeys, cfg) // Checking if the bank keys exist on disk. - val bankKeysFile = File(cfg.bankPublicKeysFilename) - if (!bankKeysFile.exists()) { + var bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) + if (bankKeys == null) { runBlocking { try { doKeysRequestAndUpdateState( @@ -264,23 +264,19 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { } } logger.info("Bank keys stored at ${cfg.bankPublicKeysFilename}") - } - // bank keys made it to the disk, check if they're accepted. - val bankKeysMaybe = loadBankKeys(cfg.bankPublicKeysFilename) - if (bankKeysMaybe == null) { - throw Exception("Although previous checks, could not load the bank keys file from: ${cfg.bankPublicKeysFilename}") + bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)!! } - if (!bankKeysMaybe.accepted) { + if (!bankKeys.accepted) { // Finishing the setup by accepting the bank keys. - if (autoAcceptKeys) bankKeysMaybe.accepted = true - else bankKeysMaybe.accepted = askUserToAcceptKeys(bankKeysMaybe) + if (autoAcceptKeys) bankKeys.accepted = true + else bankKeys.accepted = askUserToAcceptKeys(bankKeys) - if (!bankKeysMaybe.accepted) { + if (!bankKeys.accepted) { throw Exception("Cannot successfully finish the setup without accepting the bank keys.") } try { - persistBankKeys(bankKeysMaybe, cfg.bankPublicKeysFilename) + persistBankKeys(bankKeys, cfg.bankPublicKeysFilename) } catch (e: Exception) { throw Exception("Could not set bank keys as accepted on disk.", e) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt @@ -29,14 +29,8 @@ import tech.libeufin.nexus.ebics.EbicsSideException import tech.libeufin.nexus.ebics.EbicsUploadException import tech.libeufin.nexus.ebics.submitPain001 import tech.libeufin.common.* -import java.io.File -import java.nio.file.Path -import java.time.Instant -import java.time.LocalDate -import java.time.ZoneId +import java.time.* import java.util.* -import kotlin.io.path.createDirectories -import kotlin.io.path.* /** * Possible stages when an error may occur. These stages diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt @@ -29,8 +29,6 @@ import java.security.interfaces.RSAPublicKey import tech.libeufin.common.* import java.nio.file.* import kotlin.io.path.* -import java.io.File -import kotlin.reflect.typeOf val JSON = Json { this.serializersModule = SerializersModule { @@ -113,15 +111,14 @@ fun generateNewKeys(): ClientPrivateKeysFile = submitted_ini = false ) -private inline fun <reified T> persistJsonFile(obj: T, path: String, name: String) { +private inline fun <reified T> persistJsonFile(obj: T, path: Path, name: String) { val content = try { JSON.encodeToString(obj) } catch (e: Exception) { - throw Exception("Could not encode '${typeOf<T>()}' to JSON", e) + throw Exception("Could not encode $name", e) } - val (path, parent) = try { - val path = Path(path) - Pair(path, path.parent ?: path.absolute().parent) + val parent = try { + path.parent ?: path.absolute().parent } catch (e: Exception) { throw Exception("Could not write $name at '$path'", e) } @@ -136,7 +133,6 @@ private inline fun <reified T> persistJsonFile(obj: T, path: String, name: Strin !path.toFile().canWrite() -> throw Exception("Could not write $name at '$path': permission denied") else -> throw Exception("Could not write $name at '$path'", e) } - throw Exception("Could not write $name at '$path'", e) } } @@ -145,19 +141,18 @@ private inline fun <reified T> persistJsonFile(obj: T, path: String, name: Strin * * @param location the keys file location */ -fun persistBankKeys(keys: BankPublicKeysFile, location: String) = persistJsonFile(keys, location, "bank public keys") +fun persistBankKeys(keys: BankPublicKeysFile, location: Path) = persistJsonFile(keys, location, "bank public keys") /** * Persist the client keys file to disk * * @param location the keys file location */ -fun persistClientKeys(keys: ClientPrivateKeysFile, location: String) = persistJsonFile(keys, location, "client private keys") +fun persistClientKeys(keys: ClientPrivateKeysFile, location: Path) = persistJsonFile(keys, location, "client private keys") -private inline fun <reified T> loadJsonFile(path: String, name: String): T? { +private inline fun <reified T> loadJsonFile(path: Path, name: String): T? { val content = try { - val path = Path(path) path.readText() } catch (e: Exception) { when { @@ -180,7 +175,7 @@ private inline fun <reified T> loadJsonFile(path: String, name: String): T? { * @return the internal JSON representation of the keys file, * or null if the file does not exist */ -fun loadBankKeys(location: String): BankPublicKeysFile? = loadJsonFile(location, "bank public keys") +fun loadBankKeys(location: Path): BankPublicKeysFile? = loadJsonFile(location, "bank public keys") /** * Load the client keys file from disk. @@ -189,7 +184,7 @@ fun loadBankKeys(location: String): BankPublicKeysFile? = loadJsonFile(location, * @return the internal JSON representation of the keys file, * or null if the file does not exist */ -fun loadClientKeys(location: String): ClientPrivateKeysFile? = loadJsonFile(location, "client private keys") +fun loadClientKeys(location: Path): ClientPrivateKeysFile? = loadJsonFile(location, "client private keys") /** * Load client and bank keys from disk. diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt @@ -50,8 +50,8 @@ class CliTest { val allCmds = listOf("ebics-submit", "ebics-fetch", "ebics-setup") val conf = "conf/test.conf" val cfg = loadConfig(conf) - val clientKeysPath = Path(cfg.requireString("nexus-ebics", "client_private_keys_file")) - val bankKeysPath = Path(cfg.requireString("nexus-ebics", "bank_public_keys_file")) + val clientKeysPath = cfg.requirePath("nexus-ebics", "client_private_keys_file") + val bankKeysPath = cfg.requirePath("nexus-ebics", "bank_public_keys_file") clientKeysPath.parent!!.createDirectories() clientKeysPath.parent!!.toFile().setWritable(true) bankKeysPath.parent!!.createDirectories() @@ -72,7 +72,7 @@ class CliTest { nexusCmd.testErr("$cmd -c $conf", "Could not read client private keys at '$clientKeysPath': permission denied") } // Unfinished client - persistClientKeys(generateNewKeys(), clientKeysPath.toString()) + persistClientKeys(generateNewKeys(), clientKeysPath) for (cmd in cmds) { nexusCmd.testErr("$cmd -c $conf", "Unsubmitted client private keys, run 'libeufin-nexus ebics-setup' first") } @@ -81,7 +81,7 @@ class CliTest { persistClientKeys(generateNewKeys().apply { submitted_hia = true submitted_ini = true - }, clientKeysPath.toString()) + }, clientKeysPath) bankKeysPath.deleteIfExists() for (cmd in cmds) { nexusCmd.testErr("$cmd -c $conf", "Missing bank public keys at '$bankKeysPath', run 'libeufin-nexus ebics-setup' first") @@ -101,7 +101,7 @@ class CliTest { bank_authentication_public_key = CryptoUtil.generateRsaKeyPair(2048).public, bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2048).public, accepted = false - ), bankKeysPath.toString()) + ), bankKeysPath) for (cmd in cmds) { nexusCmd.testErr("$cmd -c $conf", "Unaccepted bank public keys, run 'libeufin-nexus ebics-setup' until accepting the bank keys") } diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt @@ -43,7 +43,7 @@ fun prepDb(cfg: TalerConfig): Database { cfg.loadDefaults() val dbCfg = DatabaseConfig( dbConnStr = "postgresql:///libeufincheck", - sqlDir = cfg.requirePath("paths", "datadir") + "sql" + sqlDir = cfg.requirePath("paths", "datadir").resolve("sql") ) pgDataSource(dbCfg.dbConnStr).pgConnection().use { conn -> println("SQL dir for testing: ${dbCfg.sqlDir}") diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt @@ -27,11 +27,8 @@ import tech.libeufin.nexus.* import tech.libeufin.nexus.ebics.* import tech.libeufin.ebics.XMLUtil import tech.libeufin.ebics.ebics_h004.EbicsUnsecuredRequest -import java.io.File -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue +import kotlin.test.* +import kotlin.io.path.* class Ebics { // Checks XML is valid and INI. @@ -83,6 +80,6 @@ class Ebics { @Test fun keysPdf() { val pdf = generateKeysPdf(clientKeys, config) - File("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf) + Path("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf) } } \ No newline at end of file diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt @@ -20,7 +20,7 @@ import org.junit.Test import tech.libeufin.nexus.* import tech.libeufin.common.CryptoUtil -import java.io.File +import kotlin.io.path.* import kotlin.test.* class PublicKeys { @@ -42,9 +42,9 @@ class PublicKeys { bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2028).public ) // storing them on disk. - persistBankKeys(fileContent, "/tmp/nexus-tests-bank-keys.json") + persistBankKeys(fileContent, Path("/tmp/nexus-tests-bank-keys.json")) // loading them and check that values are the same. - val fromDisk = loadBankKeys("/tmp/nexus-tests-bank-keys.json") + val fromDisk = loadBankKeys(Path("/tmp/nexus-tests-bank-keys.json")) assertNotNull(fromDisk) assertTrue { fromDisk.accepted && @@ -54,14 +54,13 @@ class PublicKeys { } @Test fun loadNotFound() { - assertNull(loadBankKeys("/tmp/highly-unlikely-to-be-found.json")) + assertNull(loadBankKeys(Path("/tmp/highly-unlikely-to-be-found.json"))) } } class PrivateKeys { - val f = File("/tmp/nexus-privs-test.json") + val f = Path("/tmp/nexus-privs-test.json") init { - if (f.exists()) - f.delete() + f.deleteIfExists() } /** @@ -70,9 +69,9 @@ class PrivateKeys { */ @Test fun load() { - assertFalse(f.exists()) - persistClientKeys(clientKeys, f.path) // Artificially storing this to the file. - val fromDisk = loadClientKeys(f.path) // loading it via the tested routine. + assert(f.notExists()) + persistClientKeys(clientKeys, f) // Artificially storing this to the file. + val fromDisk = loadClientKeys(f) // loading it via the tested routine. assertNotNull(fromDisk) // Checking the values from disk match the initial object. assertTrue { @@ -87,6 +86,6 @@ class PrivateKeys { // Testing failure on file not found. @Test fun loadNotFound() { - assertNull(loadClientKeys("/tmp/highly-unlikely-to-be-found.json")) + assertNull(loadClientKeys(Path("/tmp/highly-unlikely-to-be-found.json"))) } } \ No newline at end of file diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt @@ -30,12 +30,10 @@ import com.github.ajalt.clikt.testing.* import io.ktor.client.* import io.ktor.client.engine.cio.* import kotlin.test.* -import java.io.File -import java.nio.file.* +import kotlin.io.path.* import java.time.Instant import kotlinx.coroutines.runBlocking import io.ktor.client.request.* -import kotlin.io.path.* fun randBytes(lenght: Int): ByteArray { val bytes = ByteArray(lenght) @@ -85,8 +83,8 @@ class Cli : CliktCommand("Run integration tests on banks provider") { val ebicsFlags = "$flags --transient --debug-ebics test/$name" val cfg = loadConfig(conf) - val clientKeysPath = Path(cfg.requirePath("nexus-ebics", "client_private_keys_file")) - val bankKeysPath = Path(cfg.requirePath("nexus-ebics", "bank_public_keys_file")) + val clientKeysPath = cfg.requirePath("nexus-ebics", "client_private_keys_file") + val bankKeysPath = cfg.requirePath("nexus-ebics", "bank_public_keys_file") var hasClientKeys = clientKeysPath.exists() var hasBankKeys = bankKeysPath.exists() diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt @@ -23,7 +23,6 @@ import tech.libeufin.nexus.* import tech.libeufin.nexus.Database as NexusDb import tech.libeufin.bank.db.AccountDAO.* import tech.libeufin.common.* -import java.io.File import java.time.Instant import java.util.Arrays import java.sql.SQLException @@ -32,6 +31,7 @@ import com.github.ajalt.clikt.testing.test import com.github.ajalt.clikt.core.CliktCommand import org.postgresql.jdbc.PgConnection import kotlin.test.* +import kotlin.io.path.* import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* @@ -142,7 +142,7 @@ class IntegrationTest { val fiatPayTo = IbanPayto(genIbanPaytoUri()) // Load conversion setup manually as the server would refuse to start without an exchange account - val sqlProcedures = File("../database-versioning/libeufin-conversion-setup.sql") + val sqlProcedures = Path("../database-versioning/libeufin-conversion-setup.sql") db.conn { it.execSQLUpdate(sqlProcedures.readText()) it.execSQLUpdate("SET search_path TO libeufin_nexus;")