libeufin

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

commit 82133eede8a316c975231f984bb34d13f08914c7
parent 17aabee238c639435cc0f507095e05691ef26eaf
Author: Florian Dold <florian.dold@gmail.com>
Date:   Wed,  3 Jun 2020 13:17:50 +0530

upgrade dependencies, use ExposedBlob instead of SerialBlob

Diffstat:
Mnexus/build.gradle | 9+++++++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 6++++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 16++++++++--------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 38+++++++++++++++++++++++++-------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 65+++++++++++++++++++++++++++++++++++++++--------------------------
Msandbox/build.gradle | 6+++++-
Msandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt | 37+++++++++++++++++++++++++++++++++----
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 97+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 8++++----
Mutil/build.gradle | 7+++++--
Mutil/src/main/kotlin/DBTypes.kt | 4+---
Dutil/src/main/kotlin/ParametersChecks.kt | 41-----------------------------------------
Dutil/src/main/kotlin/blob.kt | 8--------
13 files changed, 179 insertions(+), 163 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -54,11 +54,10 @@ compileTestKotlin { def ktor_version = "1.3.2" - +def exposed_version = "0.24.1" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "org.jetbrains.exposed:exposed:0.17.6" implementation "ch.qos.logback:logback-classic:1.2.3" implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' implementation "javax.xml.bind:jaxb-api:2.3.0" @@ -74,6 +73,12 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' + // Exposed, an SQL library + implementation "org.jetbrains.exposed:exposed-core:$exposed_version" + implementation "org.jetbrains.exposed:exposed-dao:$exposed_version" + implementation "org.jetbrains.exposed:exposed-jdbc:$exposed_version" + + // Ktor, an HTTP client and server library implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-client-apache:$ktor_version" implementation "io.ktor:ktor-server-netty:$ktor_version" diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -2,14 +2,16 @@ package tech.libeufin.nexus import io.ktor.http.HttpStatusCode import org.jetbrains.exposed.dao.* +import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.StdOutSqlLogger import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction -import tech.libeufin.nexus.NexusBankConnectionsTable.entityId -import tech.libeufin.nexus.NexusBankConnectionsTable.primaryKey import tech.libeufin.util.EbicsInitState import tech.libeufin.util.amount import java.sql.Connection diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt @@ -5,6 +5,7 @@ import io.ktor.http.HttpStatusCode import io.ktor.request.ApplicationRequest import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.statements.api.ExposedBlob import org.jetbrains.exposed.sql.transactions.transaction import org.w3c.dom.Document import tech.libeufin.util.* @@ -16,7 +17,6 @@ import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.util.* -import javax.sql.rowset.serial.SerialBlob fun isProduction(): Boolean { return System.getenv("NEXUS_PRODUCTION") != null @@ -64,13 +64,13 @@ fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): EbicsC var bankAuthPubValue: RSAPublicKey? = null if (subscriber.bankAuthenticationPublicKey != null) { bankAuthPubValue = CryptoUtil.loadRsaPublicKey( - subscriber.bankAuthenticationPublicKey?.toByteArray()!! + subscriber.bankAuthenticationPublicKey?.bytes!! ) } var bankEncPubValue: RSAPublicKey? = null if (subscriber.bankEncryptionPublicKey != null) { bankEncPubValue = CryptoUtil.loadRsaPublicKey( - subscriber.bankEncryptionPublicKey?.toByteArray()!! + subscriber.bankEncryptionPublicKey?.bytes!! ) } return EbicsClientSubscriberDetails( @@ -82,9 +82,9 @@ fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): EbicsC userId = subscriber.userID, partnerId = subscriber.partnerID, - customerSignPriv = CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()), - customerAuthPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()), - customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()), + customerSignPriv = CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.bytes), + customerAuthPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.bytes), + customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.bytes), ebicsIniState = subscriber.ebicsIniState, ebicsHiaState = subscriber.ebicsHiaState ) @@ -157,7 +157,7 @@ fun ingestBankMessagesIntoAccount( (NexusBankMessagesTable.id greater acct.highestSeenBankMessageId) }.orderBy(Pair(NexusBankMessagesTable.id, SortOrder.ASC)).forEach { // FIXME: check if it's CAMT first! - val doc = XMLUtil.parseStringIntoDom(it.message.toByteArray().toString(Charsets.UTF_8)) + val doc = XMLUtil.parseStringIntoDom(it.message.bytes.toString(Charsets.UTF_8)) processCamtMessage(bankAccountId, doc) lastId = it.id.value } @@ -209,7 +209,7 @@ suspend fun fetchEbicsC5x( this.bankConnection = conn this.code = "C53" this.messageId = msgId - this.message = SerialBlob(it.second.toByteArray(Charsets.UTF_8)) + this.message = ExposedBlob(it.second.toByteArray(Charsets.UTF_8)) } } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -60,6 +60,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.time.delay import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.statements.api.ExposedBlob import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -73,7 +74,6 @@ import java.time.Duration import java.util.* import java.util.zip.InflaterInputStream import javax.crypto.EncryptedPrivateKeyInfo -import javax.sql.rowset.serial.SerialBlob import java.time.LocalDateTime data class NexusError(val statusCode: HttpStatusCode, val reason: String) : @@ -176,9 +176,9 @@ fun createEbicsBankConnectionFromBackup( hostID = ebicsBackup.hostID partnerID = ebicsBackup.partnerID userID = ebicsBackup.userID - signaturePrivateKey = SerialBlob(sigKey.encoded) - encryptionPrivateKey = SerialBlob(encKey.encoded) - authenticationPrivateKey = SerialBlob(authKey.encoded) + signaturePrivateKey = ExposedBlob(sigKey.encoded) + encryptionPrivateKey = ExposedBlob((encKey.encoded)) + authenticationPrivateKey = ExposedBlob((authKey.encoded)) nexusBankConnection = bankConn ebicsIniState = EbicsInitState.UNKNOWN ebicsHiaState = EbicsInitState.UNKNOWN @@ -222,9 +222,9 @@ fun createEbicsBankConnection(bankConnectionName: String, user: NexusUserEntity, partnerID = newTransportData.partnerID userID = newTransportData.userID systemID = newTransportData.systemID - signaturePrivateKey = SerialBlob(pairA.private.encoded) - encryptionPrivateKey = SerialBlob(pairB.private.encoded) - authenticationPrivateKey = SerialBlob(pairC.private.encoded) + signaturePrivateKey = ExposedBlob((pairA.private.encoded)) + encryptionPrivateKey = ExposedBlob((pairB.private.encoded)) + authenticationPrivateKey = ExposedBlob((pairC.private.encoded)) nexusBankConnection = bankConn ebicsIniState = EbicsInitState.NOT_SENT ebicsHiaState = EbicsInitState.NOT_SENT @@ -278,6 +278,18 @@ suspend fun downloadFacadesTransactions(coroutineScope: CoroutineScope) { } } +fun <T>expectNonNull(param: T?): T { + return param ?: throw EbicsProtocolError( + HttpStatusCode.BadRequest, + "Non-null value expected." + ) +} + +fun ApplicationCall.expectUrlParameter(name: String): String { + return this.request.queryParameters[name] + ?: throw EbicsProtocolError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI") +} + suspend fun fetchTransactionsInternal( client: HttpClient, user: NexusUserEntity, @@ -860,8 +872,8 @@ fun serverMain(dbName: String) { subscriberEntity.ebicsHiaState = EbicsInitState.SENT } if (hpbData != null) { - subscriberEntity.bankAuthenticationPublicKey = SerialBlob(hpbData.authenticationPubKey.encoded) - subscriberEntity.bankEncryptionPublicKey = SerialBlob(hpbData.encryptionPubKey.encoded) + subscriberEntity.bankAuthenticationPublicKey = ExposedBlob((hpbData.authenticationPubKey.encoded)) + subscriberEntity.bankEncryptionPublicKey = ExposedBlob((hpbData.encryptionPubKey.encoded)) } } call.respond(object {}) @@ -872,7 +884,7 @@ fun serverMain(dbName: String) { val list = BankMessageList() val conn = requireBankConnection(call, "connid") NexusBankMessageEntity.find { NexusBankMessagesTable.bankConnection eq conn.id }.map { - list.bankMessages.add(BankMessageInfo(it.messageId, it.code, it.message.length())) + list.bankMessages.add(BankMessageInfo(it.messageId, it.code, it.message.bytes.size.toLong())) } list } @@ -890,7 +902,7 @@ fun serverMain(dbName: String) { throw NexusError(HttpStatusCode.NotFound, "bank message not found") } return@transaction object { - val msgContent = msg.message.toByteArray() + val msgContent = msg.message.bytes } } call.respondBytes(ret.msgContent, ContentType("application", "xml")) @@ -974,8 +986,8 @@ fun serverMain(dbName: String) { val conn = requireBankConnection(call, "connid") val subscriber = EbicsSubscriberEntity.find { EbicsSubscribersTable.nexusBankConnection eq conn.id }.first() - subscriber.bankAuthenticationPublicKey = SerialBlob(hpbData.authenticationPubKey.encoded) - subscriber.bankEncryptionPublicKey = SerialBlob(hpbData.encryptionPubKey.encoded) + subscriber.bankAuthenticationPublicKey = ExposedBlob((hpbData.authenticationPubKey.encoded)) + subscriber.bankEncryptionPublicKey = ExposedBlob((hpbData.encryptionPubKey.encoded)) } call.respond(object {}) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt @@ -12,16 +12,13 @@ import io.ktor.response.respondText import io.ktor.routing.Route import io.ktor.routing.get import io.ktor.routing.post -import org.apache.http.client.methods.RequestBuilder.post import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.IdTable +import org.jetbrains.exposed.dao.id.IdTable import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction -import tech.libeufin.util.* -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.concurrent.atomic.LongAdder +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.EbicsProtocolError +import tech.libeufin.util.parseAmount import kotlin.math.abs import kotlin.math.min @@ -53,6 +50,7 @@ data class TalerIncomingBankTransaction( data class TalerIncomingHistory( var incoming_transactions: MutableList<TalerIncomingBankTransaction> = mutableListOf() ) + data class TalerOutgoingBankTransaction( val row_id: Long, val date: GnunetTimestamp, // timestamp @@ -83,6 +81,7 @@ data class TalerAdminAddIncoming( data class GnunetTimestamp( val t_ms: Long ) + data class TalerAddIncomingResponse( val timestamp: GnunetTimestamp, val row_id: Long @@ -146,6 +145,7 @@ fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int): List<T> { fun buildPaytoUri(name: String, iban: String, bic: String): String { return "payto://iban/$bic/$iban?name=$name" } + fun buildPaytoUri(iban: String, bic: String): String { return "payto://iban/$bic/$iban" } @@ -162,23 +162,22 @@ fun getComparisonOperator(delta: Int, start: Long, table: IdTable<Long>): Op<Boo } } } + +fun expectLong(param: String?): Long { + if (param == null) { + throw EbicsProtocolError(HttpStatusCode.BadRequest, "'$param' is not Long") + } + return try { + param.toLong() + } catch (e: Exception) { + throw EbicsProtocolError(HttpStatusCode.BadRequest, "'$param' is not Long") + } +} + + /** Helper handling 'start' being optional and its dependence on 'delta'. */ fun handleStartArgument(start: String?, delta: Int): Long { - return expectLong(start) ?: if (delta >= 0) { - /** - * Using -1 as the smallest value, as some DBMS might use 0 and some - * others might use 1 as the smallest row id. - */ - -1 - } else { - /** - * NOTE: the database currently enforces there MAX_VALUE is always - * strictly greater than any row's id in the database. In fact, the - * database throws exception whenever a new row is going to occupy - * the MAX_VALUE with its id. - */ - Long.MAX_VALUE - } + return expectLong(start) } /** @@ -405,13 +404,19 @@ fun ingestTalerTransactions() { } suspend fun historyOutgoing(call: ApplicationCall): Unit { - val delta: Int = expectInt(call.expectUrlParameter("delta")) + val param = call.expectUrlParameter("delta") + val delta: Int = try { + param.toInt() + } catch (e: Exception) { + throw EbicsProtocolError(HttpStatusCode.BadRequest, "'${param}' is not Int") + } val start: Long = handleStartArgument(call.request.queryParameters["start"], delta) val startCmpOp = getComparisonOperator(delta, start, TalerRequestedPayments) /* retrieve database elements */ val history = TalerOutgoingHistory() transaction { val user = authenticateRequest(call.request) + /** Retrieve all the outgoing payments from the _clean Taler outgoing table_ */ val subscriberBankAccount = getFacadeBankAccount(user) val reqPayments = TalerRequestedPaymentEntity.find { @@ -424,8 +429,11 @@ suspend fun historyOutgoing(call: ApplicationCall): Unit { row_id = it.id.value, amount = it.amount, wtid = it.wtid, - date = GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError( - HttpStatusCode.InternalServerError, "Null value met after check, VERY strange.")), + date = GnunetTimestamp( + it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError( + HttpStatusCode.InternalServerError, "Null value met after check, VERY strange." + ) + ), credit_account = it.creditAccount, debit_account = buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode), exchange_base_url = "FIXME-to-request-along-subscriber-registration" @@ -442,7 +450,12 @@ suspend fun historyOutgoing(call: ApplicationCall): Unit { // /taler/history/incoming suspend fun historyIncoming(call: ApplicationCall): Unit { - val delta: Int = expectInt(call.expectUrlParameter("delta")) + val param = call.expectUrlParameter("delta") + val delta: Int = try { + param.toInt() + } catch (e: Exception) { + throw EbicsProtocolError(HttpStatusCode.BadRequest, "'${param}' is not Int") + } val start: Long = handleStartArgument(call.request.queryParameters["start"], delta) val history = TalerIncomingHistory() val startCmpOp = getComparisonOperator(delta, start, TalerIncomingPayments) diff --git a/sandbox/build.gradle b/sandbox/build.gradle @@ -41,10 +41,10 @@ sourceSets { } def ktor_version = "1.3.2" +def exposed_version = "0.24.1" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "org.jetbrains.exposed:exposed:0.17.6" implementation "ch.qos.logback:logback-classic:1.2.3" implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' implementation "javax.xml.bind:jaxb-api:2.3.0" @@ -56,6 +56,10 @@ dependencies { implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20' + implementation "org.jetbrains.exposed:exposed-core:$exposed_version" + implementation "org.jetbrains.exposed:exposed-dao:$exposed_version" + implementation "org.jetbrains.exposed:exposed-jdbc:$exposed_version" + implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-client-apache:$ktor_version" implementation "io.ktor:ktor-server-netty:$ktor_version" diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -19,8 +19,17 @@ package tech.libeufin.sandbox -import org.jetbrains.exposed.dao.* -import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction import java.sql.Connection @@ -85,6 +94,7 @@ object EbicsSubscriberPublicKeysTable : IntIdTable() { val rsaPublicKey = blob("rsaPublicKey") val state = enumeration("state", KeyState::class) } + class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsSubscriberPublicKeyEntity>(EbicsSubscriberPublicKeysTable) @@ -102,8 +112,10 @@ object EbicsHostsTable : IntIdTable() { val encryptionPrivateKey = blob("encryptionPrivateKey") val authenticationPrivateKey = blob("authenticationPrivateKey") } + class EbicsHostEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsHostEntity>(EbicsHostsTable) + var hostId by EbicsHostsTable.hostID var ebicsVersion by EbicsHostsTable.ebicsVersion var signaturePrivateKey by EbicsHostsTable.signaturePrivateKey @@ -125,8 +137,10 @@ object EbicsSubscribersTable : IntIdTable() { val nextOrderID = integer("nextOrderID") val state = enumeration("state", SubscriberState::class) } + class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsSubscriberEntity>(EbicsSubscribersTable) + var userId by EbicsSubscribersTable.userId var partnerId by EbicsSubscribersTable.partnerId var systemId by EbicsSubscribersTable.systemId @@ -152,8 +166,10 @@ object EbicsDownloadTransactionsTable : IdTable<String>() { val segmentSize = integer("segmentSize") val receiptReceived = bool("receiptReceived") } + class EbicsDownloadTransactionEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, EbicsDownloadTransactionEntity>(EbicsDownloadTransactionsTable) + var orderType by EbicsDownloadTransactionsTable.orderType var host by EbicsHostEntity referencedOn EbicsDownloadTransactionsTable.host var subscriber by EbicsSubscriberEntity referencedOn EbicsDownloadTransactionsTable.subscriber @@ -177,8 +193,10 @@ object EbicsUploadTransactionsTable : IdTable<String>() { val lastSeenSegment = integer("lastSeenSegment") val transactionKeyEnc = blob("transactionKeyEnc") } + class EbicsUploadTransactionEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, EbicsUploadTransactionEntity>(EbicsUploadTransactionsTable) + var orderType by EbicsUploadTransactionsTable.orderType var orderID by EbicsUploadTransactionsTable.orderID var host by EbicsHostEntity referencedOn EbicsUploadTransactionsTable.host @@ -199,8 +217,10 @@ object EbicsOrderSignaturesTable : IntIdTable() { val signatureAlgorithm = text("signatureAlgorithm") val signatureValue = blob("signatureValue") } + class EbicsOrderSignatureEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsOrderSignatureEntity>(EbicsOrderSignaturesTable) + var orderID by EbicsOrderSignaturesTable.orderID var orderType by EbicsOrderSignaturesTable.orderType var partnerID by EbicsOrderSignaturesTable.partnerID @@ -218,8 +238,10 @@ object EbicsUploadTransactionChunksTable : IdTable<String>() { val chunkIndex = integer("chunkIndex") val chunkContent = blob("chunkContent") } + class EbicsUploadTransactionChunkEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, EbicsUploadTransactionChunkEntity>(EbicsUploadTransactionChunksTable) + var chunkIndex by EbicsUploadTransactionChunksTable.chunkIndex var chunkContent by EbicsUploadTransactionChunksTable.chunkContent } @@ -234,13 +256,18 @@ object PaymentsTable : IntIdTable() { val amount = text("amount") val date = long("date") } + class PaymentEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<PaymentEntity>(PaymentsTable) + var creditorIban by PaymentsTable.creditorIban var debitorIban by PaymentsTable.debitorIban var subject by PaymentsTable.subject - var amount by PaymentsTable.amount /** in the CURRENCY:X.Y format */ - var date by PaymentsTable.date /** Date when the payment was persisted in this system. */ + var amount by PaymentsTable.amount + + /** in the CURRENCY:X.Y format */ + var date by PaymentsTable.date + /** Date when the payment was persisted in this system. */ } /** @@ -254,8 +281,10 @@ object BankAccountsTable : IntIdTable() { val label = text("label") val subscriber = reference("subscriber", EbicsSubscribersTable) } + class BankAccountEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<BankAccountEntity>(BankAccountsTable) + var iban by BankAccountsTable.iban var bic by BankAccountsTable.bic var name by BankAccountsTable.name diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -28,38 +28,23 @@ import io.ktor.response.respond import io.ktor.response.respondText import org.apache.xml.security.binding.xmldsig.RSAKeyValueType import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.api.ExposedBlob import org.jetbrains.exposed.sql.transactions.transaction import org.w3c.dom.Document +import tech.libeufin.util.* +import tech.libeufin.util.XMLUtil.Companion.signEbicsResponse import tech.libeufin.util.ebics_h004.* import tech.libeufin.util.ebics_hev.HEVResponse import tech.libeufin.util.ebics_hev.SystemReturnCodeType import tech.libeufin.util.ebics_s001.SignatureTypes import tech.libeufin.util.ebics_s001.UserSignatureData -import tech.libeufin.util.CryptoUtil -import tech.libeufin.util.EbicsOrderUtil -import tech.libeufin.util.XMLUtil -import tech.libeufin.util.* -import tech.libeufin.util.XMLUtil.Companion.signEbicsResponse -import java.io.ByteArrayOutputStream -import java.math.BigDecimal import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey +import java.time.Instant +import java.time.LocalDateTime import java.util.* import java.util.zip.DeflaterInputStream import java.util.zip.InflaterInputStream -import javax.sql.rowset.serial.SerialBlob -import javax.xml.datatype.DatatypeFactory -import org.apache.commons.compress.utils.IOUtils -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import java.io.BufferedInputStream -import java.io.ByteArrayInputStream -import java.nio.charset.Charset -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import javax.xml.datatype.XMLGregorianCalendar open class EbicsRequestError(errorText: String, errorCode: String) : @@ -142,6 +127,13 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement( respondText(text, ContentType.Application.Xml, HttpStatusCode.OK) } +fun <T>expectNonNull(x: T?): T { + if (x == null) { + throw EbicsProtocolError(HttpStatusCode.BadRequest, "expected non-null value") + } + return x; +} + /** * Returns a list of camt strings. Note: each element in the * list accounts for only one payment in the history. In other @@ -172,7 +164,10 @@ fun buildCamtString(type: Int, history: MutableList<RawPayment>): MutableList<St root("Document") { attribute("xmlns", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02") attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - attribute("xsi:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 camt.053.001.02.xsd") + attribute( + "xsi:schemaLocation", + "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 camt.053.001.02.xsd" + ) element("BkToCstmrStmt") { element("GrpHdr") { element("MsgId") { @@ -418,10 +413,11 @@ private fun constructCamtResponse( transaction { PaymentEntity.find { PaymentsTable.creditorIban eq bankAccount.iban or - (PaymentsTable.debitorIban eq bankAccount.iban) /** - FIXME! - and (PaymentsTable.date.between(start.millis, end.millis)) - */ + (PaymentsTable.debitorIban eq bankAccount.iban) + /** + FIXME! + and (PaymentsTable.date.between(start.millis, end.millis)) + */ }.forEach { history.add( RawPayment( @@ -504,11 +500,11 @@ private suspend fun ApplicationCall.handleEbicsHia(header: EbicsUnsecuredRequest throw EbicsInvalidRequestError() } ebicsSubscriber.authenticationKey = EbicsSubscriberPublicKeyEntity.new { - this.rsaPublicKey = SerialBlob(authPub.encoded) + this.rsaPublicKey = ExposedBlob(authPub.encoded) state = KeyState.NEW } ebicsSubscriber.encryptionKey = EbicsSubscriberPublicKeyEntity.new { - this.rsaPublicKey = SerialBlob(encPub.encoded) + this.rsaPublicKey = ExposedBlob(encPub.encoded) state = KeyState.NEW } ebicsSubscriber.state = when (ebicsSubscriber.state) { @@ -539,7 +535,7 @@ private suspend fun ApplicationCall.handleEbicsIni(header: EbicsUnsecuredRequest throw EbicsInvalidRequestError() } ebicsSubscriber.signatureKey = EbicsSubscriberPublicKeyEntity.new { - this.rsaPublicKey = SerialBlob(sigPub.encoded) + this.rsaPublicKey = ExposedBlob(sigPub.encoded) state = KeyState.NEW } ebicsSubscriber.state = when (ebicsSubscriber.state) { @@ -570,16 +566,16 @@ private suspend fun ApplicationCall.handleEbicsHpb( val encPubBlob = ebicsSubscriber.encryptionKey!!.rsaPublicKey val sigPubBlob = ebicsSubscriber.signatureKey!!.rsaPublicKey SubscriberKeys( - CryptoUtil.loadRsaPublicKey(authPubBlob.toByteArray()), - CryptoUtil.loadRsaPublicKey(encPubBlob.toByteArray()), - CryptoUtil.loadRsaPublicKey(sigPubBlob.toByteArray()) + CryptoUtil.loadRsaPublicKey(authPubBlob.bytes), + CryptoUtil.loadRsaPublicKey(encPubBlob.bytes), + CryptoUtil.loadRsaPublicKey(sigPubBlob.bytes) ) } val validationResult = XMLUtil.verifyEbicsDocument(requestDocument, subscriberKeys.authenticationPublicKey) LOGGER.info("validationResult: $validationResult") if (!validationResult) { - throw EbicsKeyManagementError("invalid signature", "90000"); + throw EbicsKeyManagementError("invalid signature", "90000") } val hpbRespondeData = HPBResponseOrderData().apply { this.authenticationPubKeyInfo = EbicsTypes.AuthenticationPubKeyInfoType().apply { @@ -622,8 +618,8 @@ private fun ApplicationCall.ensureEbicsHost(requestHostID: String): EbicsHostPub LOGGER.warn("client requested unknown HostID ${requestHostID}") throw EbicsKeyManagementError("[EBICS_INVALID_HOST_ID]", "091011") } - val encryptionPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.toByteArray()) - val authenticationPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.authenticationPrivateKey.toByteArray()) + val encryptionPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.bytes) + val authenticationPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.authenticationPrivateKey.bytes) EbicsHostPublicInfo( requestHostID, CryptoUtil.getRsaPublicFromPrivate(encryptionPrivateKey), @@ -797,7 +793,7 @@ private fun handleEbicsDownloadTransactionInitialization(requestContext: Request this.host = requestContext.ebicsHost this.orderType = orderType this.segmentSize = segmentSize - this.transactionKeyEnc = SerialBlob(enc.encryptedTransactionKey) + this.transactionKeyEnc = ExposedBlob(enc.encryptedTransactionKey) this.encodedResponse = encodedResponse this.numSegments = numSegments this.receiptReceived = false @@ -847,7 +843,7 @@ private fun handleEbicsUploadTransactionInitialization(requestContext: RequestCo this.orderType = orderType this.orderID = orderID this.numSegments = numSegments.toInt() - this.transactionKeyEnc = SerialBlob(transactionKeyEnc) + this.transactionKeyEnc = ExposedBlob(transactionKeyEnc) } val sigObj = XMLUtil.convertStringToJaxb<UserSignatureData>(plainSigData.toString(Charsets.UTF_8)) println("got UserSignatureData: ${plainSigData.toString(Charsets.UTF_8)}") @@ -859,7 +855,7 @@ private fun handleEbicsUploadTransactionInitialization(requestContext: RequestCo this.partnerID = sig.partnerID this.userID = sig.userID this.signatureAlgorithm = sig.signatureVersion - this.signatureValue = SerialBlob(sig.signatureValue) + this.signatureValue = ExposedBlob(sig.signatureValue) } } return EbicsResponse.createForUploadInitializationPhase(transactionID, orderID) @@ -875,7 +871,7 @@ private fun handleEbicsUploadTransactionTransmission(requestContext: RequestCont val encOrderData = requestObject.body.dataTransfer?.orderData ?: throw EbicsInvalidRequestError() val zippedData = CryptoUtil.decryptEbicsE002( - uploadTransaction.transactionKeyEnc.toByteArray(), + uploadTransaction.transactionKeyEnc.bytes, Base64.getDecoder().decode(encOrderData), requestContext.hostEncPriv ) @@ -883,18 +879,22 @@ private fun handleEbicsUploadTransactionTransmission(requestContext: RequestCont InflaterInputStream(zippedData.inputStream()).use { it.readAllBytes() } logger.debug("got upload data: ${unzippedData.toString(Charsets.UTF_8)}") - val sigs = EbicsOrderSignatureEntity.find { + val sigs = EbicsOrderSignatureEntity.find { (EbicsOrderSignaturesTable.orderID eq uploadTransaction.orderID) and (EbicsOrderSignaturesTable.orderType eq uploadTransaction.orderType) } - if (sigs.count() == 0) { + if (sigs.count() == 0L) { throw EbicsInvalidRequestError() } for (sig in sigs) { if (sig.signatureAlgorithm == "A006") { val signedData = CryptoUtil.digestEbicsOrderA006(unzippedData) - val res1 = CryptoUtil.verifyEbicsA006(sig.signatureValue.toByteArray(), signedData, requestContext.clientSigPub) + val res1 = CryptoUtil.verifyEbicsA006( + sig.signatureValue.bytes, + signedData, + requestContext.clientSigPub + ) if (!res1) { throw EbicsInvalidRequestError() @@ -954,19 +954,17 @@ private fun makeReqestContext(requestObject: EbicsRequest): RequestContext { throw EbicsSubscriberStateError() val hostAuthPriv = CryptoUtil.loadRsaPrivateKey( - ebicsHost.authenticationPrivateKey - .toByteArray() + ebicsHost.authenticationPrivateKey.bytes ) val hostEncPriv = CryptoUtil.loadRsaPrivateKey( - ebicsHost.encryptionPrivateKey - .toByteArray() + ebicsHost.encryptionPrivateKey.bytes ) val clientAuthPub = - CryptoUtil.loadRsaPublicKey(subscriber.authenticationKey!!.rsaPublicKey.toByteArray()) + CryptoUtil.loadRsaPublicKey(subscriber.authenticationKey!!.rsaPublicKey.bytes) val clientEncPub = - CryptoUtil.loadRsaPublicKey(subscriber.encryptionKey!!.rsaPublicKey.toByteArray()) + CryptoUtil.loadRsaPublicKey(subscriber.encryptionKey!!.rsaPublicKey.bytes) val clientSigPub = - CryptoUtil.loadRsaPublicKey(subscriber.signatureKey!!.rsaPublicKey.toByteArray()) + CryptoUtil.loadRsaPublicKey(subscriber.signatureKey!!.rsaPublicKey.bytes) return RequestContext( hostAuthPriv = hostAuthPriv, @@ -1056,7 +1054,8 @@ suspend fun ApplicationCall.ebicsweb() { } } EbicsTypes.TransactionPhaseType.RECEIPT -> { - val requestTransactionID = requestObject.header.static.transactionID ?: throw EbicsInvalidRequestError() + val requestTransactionID = + requestObject.header.static.transactionID ?: throw EbicsInvalidRequestError() if (requestContext.downloadTransaction == null) throw EbicsInvalidRequestError() val receiptCode = diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -50,7 +50,6 @@ import java.lang.ArithmeticException import java.math.BigDecimal import java.security.interfaces.RSAPublicKey import java.text.DateFormat -import javax.sql.rowset.serial.SerialBlob import javax.xml.bind.JAXBContext import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter @@ -61,6 +60,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.ktor.http.toHttpDateString +import org.jetbrains.exposed.sql.statements.api.ExposedBlob import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime @@ -272,9 +272,9 @@ fun main() { EbicsHostEntity.new { this.ebicsVersion = req.ebicsVersion this.hostId = req.hostID - this.authenticationPrivateKey = SerialBlob(pairA.private.encoded) - this.encryptionPrivateKey = SerialBlob(pairB.private.encoded) - this.signaturePrivateKey = SerialBlob(pairC.private.encoded) + this.authenticationPrivateKey = ExposedBlob(pairA.private.encoded) + this.encryptionPrivateKey = ExposedBlob(pairB.private.encoded) + this.signaturePrivateKey = ExposedBlob(pairC.private.encoded) } } diff --git a/util/build.gradle b/util/build.gradle @@ -26,9 +26,10 @@ sourceSets { main.java.srcDirs = ['src/main/java', 'src/main/kotlin'] } +def exposed_version = "0.24.1" + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "org.jetbrains.exposed:exposed:0.17.6" implementation "io.ktor:ktor-server-netty:1.2.4" implementation "ch.qos.logback:logback-classic:1.2.3" implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' @@ -38,9 +39,11 @@ dependencies { implementation "org.glassfish.jaxb:jaxb-runtime:2.3.1" implementation 'org.apache.santuario:xmlsec:2.1.4' implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.64' - implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20' + implementation "org.jetbrains.exposed:exposed-core:$exposed_version" + implementation "org.jetbrains.exposed:exposed-dao:$exposed_version" + testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.50' testImplementation 'org.jetbrains.kotlin:kotlin-test:1.3.50' diff --git a/util/src/main/kotlin/DBTypes.kt b/util/src/main/kotlin/DBTypes.kt @@ -1,7 +1,5 @@ package tech.libeufin.util -import org.jetbrains.exposed.dao.IdTable -import org.jetbrains.exposed.dao.IntIdTable import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.ColumnType import org.jetbrains.exposed.sql.Table @@ -58,6 +56,6 @@ class AmountColumnType : ColumnType() { * Make sure the number entered by upper layers does not need any rounding * to conform to scale == 2 */ -fun IdTable<*>.amount(name: String): Column<Amount> { +fun Table.amount(name: String): Column<Amount> { return registerColumn(name, AmountColumnType()) } \ No newline at end of file diff --git a/util/src/main/kotlin/ParametersChecks.kt b/util/src/main/kotlin/ParametersChecks.kt @@ -1,40 +0,0 @@ -package tech.libeufin.util - -import io.ktor.application.ApplicationCall -import io.ktor.http.HttpStatusCode - -fun expectInt(param: String): Int { - return try { - param.toInt() - } catch (e: Exception) { - throw EbicsProtocolError(HttpStatusCode.BadRequest,"'$param' is not Int") - } -} - -fun <T>expectNonNull(param: T?): T { - return param ?: throw EbicsProtocolError( - HttpStatusCode.BadRequest, - "Non-null value expected." - ) -} - -fun expectLong(param: String): Long { - return try { - param.toLong() - } catch (e: Exception) { - throw EbicsProtocolError(HttpStatusCode.BadRequest,"'$param' is not Long") - } -} - -fun expectLong(param: String?): Long? { - if (param != null) { - return expectLong(param) - } - return null -} - - -fun ApplicationCall.expectUrlParameter(name: String): String { - return this.request.queryParameters[name] - ?: throw EbicsProtocolError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI") -} -\ No newline at end of file diff --git a/util/src/main/kotlin/blob.kt b/util/src/main/kotlin/blob.kt @@ -1,7 +0,0 @@ -package tech.libeufin.util - -import java.sql.Blob - -fun Blob.toByteArray(): ByteArray { - return this.binaryStream.readAllBytes() -} -\ No newline at end of file