libeufin

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

commit 7614a3d502ded4ac8c6d491faee7e6c5533e5334
parent ceab4b823f98ea34fdec46784537ad120ae50e73
Author: Florian Dold <florian@dold.me>
Date:   Thu, 21 Jan 2021 00:55:36 +0100

database and style improvements

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 131+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 5+++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt | 49++++++++++++++++++++++++-------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt | 57+++++++++++++++++++++++++++++++++------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 55++++++++++++++++++++++++++++++++-----------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 116++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt | 19+++++++++++++++++++
9 files changed, 257 insertions(+), 181 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Auth.kt @@ -47,7 +47,7 @@ fun authenticateRequest(request: ApplicationRequest): NexusUserEntity { ) else authorization val (username, password) = extractUserAndPassword(headerLine) val user = NexusUserEntity.find { - NexusUsersTable.id eq username + NexusUsersTable.username eq username }.firstOrNull() if (user == null) { throw NexusError(HttpStatusCode.Unauthorized, "Unknown user '$username'") @@ -97,7 +97,7 @@ fun ApplicationRequest.requirePermission(vararg perms: PermissionQuery) { } var foundPermission = false for (pr in perms) { - val p = Permission("user", user.id.value, pr.resourceType, pr.resourceId, pr.permissionName) + val p = Permission("user", user.username, pr.resourceType, pr.resourceId, pr.permissionName) val existingPerm = findPermission(p) if (existingPerm != null) { foundPermission = true diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -21,8 +21,6 @@ package tech.libeufin.nexus import org.jetbrains.exposed.dao.* 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.dao.id.LongIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager @@ -41,11 +39,11 @@ import java.sql.Connection object TalerRequestedPaymentsTable : LongIdTable() { val facade = reference("facade", FacadesTable) val preparedPayment = reference("payment", PaymentInitiationsTable) - val requestUId = text("request_uid") + val requestUid = text("requestUid") val amount = text("amount") - val exchangeBaseUrl = text("exchange_base_url") + val exchangeBaseUrl = text("exchangeBaseUrl") val wtid = text("wtid") - val creditAccount = text("credit_account") + val creditAccount = text("creditAccount") } class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) { @@ -53,7 +51,7 @@ class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) { var facade by FacadeEntity referencedOn TalerRequestedPaymentsTable.facade var preparedPayment by PaymentInitiationEntity referencedOn TalerRequestedPaymentsTable.preparedPayment - var requestUId by TalerRequestedPaymentsTable.requestUId + var requestUid by TalerRequestedPaymentsTable.requestUid var amount by TalerRequestedPaymentsTable.amount var exchangeBaseUrl by TalerRequestedPaymentsTable.exchangeBaseUrl var wtid by TalerRequestedPaymentsTable.wtid @@ -71,6 +69,7 @@ object TalerIncomingPaymentsTable : LongIdTable() { val debtorPaytoUri = text("incomingPaytoUri") } + class TalerIncomingPaymentEntity(id: EntityID<Long>) : LongEntity(id) { companion object : LongEntityClass<TalerIncomingPaymentEntity>(TalerIncomingPaymentsTable) @@ -83,18 +82,20 @@ class TalerIncomingPaymentEntity(id: EntityID<Long>) : LongEntity(id) { /** * Table that stores all messages we receive from the bank. */ -object NexusBankMessagesTable : IntIdTable() { +object NexusBankMessagesTable : LongIdTable() { val bankConnection = reference("bankConnection", NexusBankConnectionsTable) - // Unique identifier for the message within the bank connection + /** + * Unique identifier for the message within the bank connection + */ val messageId = text("messageId") val code = text("code") val message = blob("message") val errors = bool("errors").default(false) // true when the parser could not ingest one message. } -class NexusBankMessageEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<NexusBankMessageEntity>(NexusBankMessagesTable) +class NexusBankMessageEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusBankMessageEntity>(NexusBankMessagesTable) var bankConnection by NexusBankConnectionEntity referencedOn NexusBankMessagesTable.bankConnection var messageId by NexusBankMessagesTable.messageId @@ -215,7 +216,7 @@ class PaymentInitiationEntity(id: EntityID<Long>) : LongEntity(id) { * This table contains the bank accounts that are offered by the bank. * The bank account label (as assigned by the bank) is the primary key. */ -object OfferedBankAccountsTable : Table() { +object OfferedBankAccountsTable : LongIdTable() { val offeredAccountId = text("offeredAccountId") val bankConnection = reference("bankConnection", NexusBankConnectionsTable) val iban = text("iban") @@ -225,15 +226,28 @@ object OfferedBankAccountsTable : Table() { // column below gets defined only WHEN the user imports the bank account. val imported = reference("imported", NexusBankAccountsTable).nullable() - override val primaryKey = PrimaryKey(offeredAccountId, bankConnection) + init { + uniqueIndex(offeredAccountId, bankConnection) + } +} + +class OfferedBankAccountEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<OfferedBankAccountEntity>(OfferedBankAccountsTable) + + var offeredAccountId by OfferedBankAccountsTable.offeredAccountId + var bankConnection by NexusBankConnectionEntity referencedOn OfferedBankAccountsTable.bankConnection + var accountHolder by OfferedBankAccountsTable.accountHolder + var iban by OfferedBankAccountsTable.iban + var bankCode by OfferedBankAccountsTable.bankCode + var imported by NexusBankAccountEntity optionalReferencedOn OfferedBankAccountsTable.imported } /** * This table holds triples of <iban, bic, holder name>. * FIXME(dold): Allow other account and bank identifications than IBAN and BIC */ -object NexusBankAccountsTable : IdTable<String>() { - override val id = text("id").entityId().uniqueIndex() +object NexusBankAccountsTable : LongIdTable() { + val bankAccountName = text("bankAccountId").uniqueIndex() val accountHolder = text("accountHolder") val iban = text("iban") val bankCode = text("bankCode") @@ -243,25 +257,30 @@ object NexusBankAccountsTable : IdTable<String>() { val lastNotificationCreationTimestamp = long("lastNotificationCreationTimestamp").nullable() // Highest bank message ID that this bank account is aware of. - val highestSeenBankMessageId = integer("highestSeenBankMessageId") + val highestSeenBankMessageSerialId = long("highestSeenBankMessageSerialId") val pain001Counter = long("pain001counter").default(1) } -class NexusBankAccountEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, NexusBankAccountEntity>(NexusBankAccountsTable) +class NexusBankAccountEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusBankAccountEntity>(NexusBankAccountsTable) { + fun findByName(name: String): NexusBankAccountEntity? { + return find { NexusBankAccountsTable.bankAccountName eq name }.firstOrNull() + } + } + var bankAccountName by NexusBankAccountsTable.bankAccountName var accountHolder by NexusBankAccountsTable.accountHolder var iban by NexusBankAccountsTable.iban var bankCode by NexusBankAccountsTable.bankCode var defaultBankConnection by NexusBankConnectionEntity optionalReferencedOn NexusBankAccountsTable.defaultBankConnection - var highestSeenBankMessageId by NexusBankAccountsTable.highestSeenBankMessageId + var highestSeenBankMessageSerialId by NexusBankAccountsTable.highestSeenBankMessageSerialId var pain001Counter by NexusBankAccountsTable.pain001Counter var lastStatementCreationTimestamp by NexusBankAccountsTable.lastStatementCreationTimestamp var lastReportCreationTimestamp by NexusBankAccountsTable.lastReportCreationTimestamp var lastNotificationCreationTimestamp by NexusBankAccountsTable.lastNotificationCreationTimestamp } -object NexusEbicsSubscribersTable : IntIdTable() { +object NexusEbicsSubscribersTable : LongIdTable() { val ebicsURL = text("ebicsURL") val hostID = text("hostID") val partnerID = text("partnerID") @@ -277,8 +296,8 @@ object NexusEbicsSubscribersTable : IntIdTable() { val ebicsHiaState = enumerationByName("ebicsHiaState", 16, EbicsInitState::class) } -class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<EbicsSubscriberEntity>(NexusEbicsSubscribersTable) +class EbicsSubscriberEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<EbicsSubscriberEntity>(NexusEbicsSubscribersTable) var ebicsURL by NexusEbicsSubscribersTable.ebicsURL var hostID by NexusEbicsSubscribersTable.hostID @@ -295,72 +314,90 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { var ebicsHiaState by NexusEbicsSubscribersTable.ebicsHiaState } -object NexusUsersTable : IdTable<String>() { - override val id = text("id").entityId().uniqueIndex() +object NexusUsersTable : LongIdTable() { + + val username = text("username") val passwordHash = text("password") val superuser = bool("superuser") } -class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable) +class NexusUserEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusUserEntity>(NexusUsersTable) + var username by NexusUsersTable.username var passwordHash by NexusUsersTable.passwordHash var superuser by NexusUsersTable.superuser } -object NexusBankConnectionsTable : IdTable<String>() { - override val id = NexusBankConnectionsTable.text("id").entityId().uniqueIndex() +object NexusBankConnectionsTable : LongIdTable() { + val connectionId = text("connectionId") val type = text("type") val owner = reference("user", NexusUsersTable) } -class NexusBankConnectionEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, NexusBankConnectionEntity>(NexusBankConnectionsTable) +class NexusBankConnectionEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusBankConnectionEntity>(NexusBankConnectionsTable) { + fun findByName(name: String): NexusBankConnectionEntity? { + return find { NexusBankConnectionsTable.connectionId eq name }.firstOrNull() + } + } + var connectionId by NexusBankConnectionsTable.connectionId var type by NexusBankConnectionsTable.type var owner by NexusUserEntity referencedOn NexusBankConnectionsTable.owner } -object FacadesTable : IdTable<String>() { - override val id = FacadesTable.text("id").entityId().uniqueIndex() +object FacadesTable : LongIdTable() { + val facadeName = text("facadeName") val type = text("type") val creator = reference("creator", NexusUsersTable) } -class FacadeEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, FacadeEntity>(FacadesTable) +class FacadeEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<FacadeEntity>(FacadesTable) { + fun findByName(name: String): FacadeEntity? { + return find { FacadesTable.facadeName eq name}.firstOrNull() + } + } + var facadeName by FacadesTable.facadeName var type by FacadesTable.type var creator by NexusUserEntity referencedOn FacadesTable.creator } -object TalerFacadeStateTable : IntIdTable() { +object TalerFacadeStateTable : LongIdTable() { val bankAccount = text("bankAccount") val bankConnection = text("bankConnection") val currency = text("currency") - /* "statement", "report", "notification" */ + /** + * "statement", "report", "notification" + **/ val reserveTransferLevel = text("reserveTransferLevel") val facade = reference("facade", FacadesTable) - // highest ID seen in the raw transactions table. - val highestSeenMsgID = long("highestSeenMsgID").default(0) + /** + * Highest ID seen in the raw transactions table. + */ + val highestSeenMsgSerialId = long("highestSeenMessageSerialId").default(0) } -class TalerFacadeStateEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<TalerFacadeStateEntity>(TalerFacadeStateTable) +class TalerFacadeStateEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<TalerFacadeStateEntity>(TalerFacadeStateTable) var bankAccount by TalerFacadeStateTable.bankAccount var bankConnection by TalerFacadeStateTable.bankConnection var currency by TalerFacadeStateTable.currency - /* "statement", "report", "notification" */ + /** + * "statement", "report", "notification" + */ var reserveTransferLevel by TalerFacadeStateTable.reserveTransferLevel var facade by FacadeEntity referencedOn TalerFacadeStateTable.facade - var highestSeenMsgID by TalerFacadeStateTable.highestSeenMsgID + var highestSeenMessageSerialId by TalerFacadeStateTable.highestSeenMsgSerialId } -object NexusScheduledTasksTable : IntIdTable() { +object NexusScheduledTasksTable : LongIdTable() { val resourceType = text("resourceType") val resourceId = text("resourceId") val taskName = text("taskName") @@ -371,8 +408,8 @@ object NexusScheduledTasksTable : IntIdTable() { val prevScheduledExecutionSec = long("lastScheduledExecutionSec").nullable() } -class NexusScheduledTaskEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<NexusScheduledTaskEntity>(NexusScheduledTasksTable) +class NexusScheduledTaskEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusScheduledTaskEntity>(NexusScheduledTasksTable) var resourceType by NexusScheduledTasksTable.resourceType var resourceId by NexusScheduledTasksTable.resourceId @@ -390,7 +427,7 @@ class NexusScheduledTaskEntity(id: EntityID<Int>) : IntEntity(id) { * * Subjects are typically of type "user", but this may change in the future. */ -object NexusPermissionsTable : IntIdTable() { +object NexusPermissionsTable : LongIdTable() { val resourceType = text("resourceType") val resourceId = text("resourceId") val subjectType = text("subjectType") @@ -402,8 +439,8 @@ object NexusPermissionsTable : IntIdTable() { } } -class NexusPermissionEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<NexusPermissionEntity>(NexusPermissionsTable) +class NexusPermissionEntity(id: EntityID<Long>) : LongEntity(id) { + companion object : LongEntityClass<NexusPermissionEntity>(NexusPermissionsTable) var resourceType by NexusPermissionsTable.resourceType var resourceId by NexusPermissionsTable.resourceId diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -103,9 +103,10 @@ class Superuser : CliktCommand("Add superuser or change pw") { } transaction { val hashedPw = hashpw(password) - val user = NexusUserEntity.findById(username) + val user = NexusUserEntity.find { NexusUsersTable.username eq username }.firstOrNull() if (user == null) { - NexusUserEntity.new(username) { + NexusUserEntity.new { + this.username = this@Superuser.username this.passwordHash = hashedPw this.superuser = true } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Scheduling.kt @@ -37,7 +37,7 @@ import java.time.Instant import java.time.ZonedDateTime private data class TaskSchedule( - val taskId: Int, + val taskId: Long, val name: String, val type: String, val resourceType: String, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt @@ -62,12 +62,16 @@ data class TalerTransferRequest( ) data class TalerTransferResponse( - // point in time when the nexus put the payment instruction into the database. + /** + * Point in time when the nexus put the payment instruction into the database. + */ val timestamp: GnunetTimestamp, val row_id: Long ) -/** History accounting data structures */ +/** + * History accounting data structures + */ data class TalerIncomingBankTransaction( val row_id: Long, val date: GnunetTimestamp, // timestamp @@ -193,7 +197,7 @@ fun extractReservePubFromSubject(rawSubject: String): String? { } private fun getTalerFacadeState(fcid: String): TalerFacadeStateEntity { - val facade = FacadeEntity.find { FacadesTable.id eq fcid }.firstOrNull() ?: throw NexusError( + val facade = FacadeEntity.find { FacadesTable.facadeName eq fcid }.firstOrNull() ?: throw NexusError( HttpStatusCode.NotFound, "Could not find facade '${fcid}'" ) @@ -207,7 +211,7 @@ private fun getTalerFacadeState(fcid: String): TalerFacadeStateEntity { private fun getTalerFacadeBankAccount(fcid: String): NexusBankAccountEntity { val facadeState = getTalerFacadeState(fcid) - return NexusBankAccountEntity.findById(facadeState.bankAccount) ?: throw NexusError( + return NexusBankAccountEntity.findByName(facadeState.bankAccount) ?: throw NexusError( HttpStatusCode.NotFound, "Could not find any bank account named ${facadeState.bankAccount}" ) @@ -219,20 +223,21 @@ private fun getTalerFacadeBankAccount(fcid: String): NexusBankAccountEntity { private suspend fun talerTransfer(call: ApplicationCall) { val transferRequest = call.receive<TalerTransferRequest>() val amountObj = parseAmount(transferRequest.amount) - val creditorObj = parsePayto(transferRequest.credit_account) + // FIXME: Right now we only parse the credit_account, should we also validate that it matches our account info? + parsePayto(transferRequest.credit_account) val facadeId = expectNonNull(call.parameters["fcid"]) val opaqueRowId = transaction { // FIXME: re-enable authentication (https://bugs.gnunet.org/view.php?id=6703) // val exchangeUser = authenticateRequest(call.request) call.request.requirePermission(PermissionQuery("facade", facadeId, "facade.talerWireGateway.transfer")) - val facade = FacadeEntity.find { FacadesTable.id eq facadeId }.firstOrNull() ?: throw NexusError( + val facade = FacadeEntity.find { FacadesTable.facadeName eq facadeId }.firstOrNull() ?: throw NexusError( HttpStatusCode.NotFound, "Could not find facade '${facadeId}'" ) val creditorData = parsePayto(transferRequest.credit_account) /** Checking the UID has the desired characteristics */ TalerRequestedPaymentEntity.find { - TalerRequestedPaymentsTable.requestUId eq transferRequest.request_uid + TalerRequestedPaymentsTable.requestUid eq transferRequest.request_uid }.forEach { if ( (it.amount != transferRequest.amount) or @@ -262,7 +267,7 @@ private suspend fun talerTransfer(call: ApplicationCall) { this.facade = facade preparedPayment = pain001 // not really used/needed, just here to silence warnings exchangeBaseUrl = transferRequest.exchange_base_url - requestUId = transferRequest.request_uid + requestUid = transferRequest.request_uid amount = transferRequest.amount wtid = transferRequest.wtid creditAccount = transferRequest.credit_account @@ -293,7 +298,7 @@ fun roundTimestamp(t: GnunetTimestamp): GnunetTimestamp { /** * Serve a /taler/admin/add-incoming */ -private suspend fun talerAddIncoming(call: ApplicationCall, httpClient: HttpClient): Unit { +private suspend fun talerAddIncoming(call: ApplicationCall, httpClient: HttpClient) { val facadeID = expectNonNull(call.parameters["fcid"]) call.request.requirePermission(PermissionQuery("facade", facadeID, "facade.talerWireGateway.addIncoming")) val addIncomingData = call.receive<TalerAdminAddIncoming>() @@ -302,7 +307,7 @@ private suspend fun talerAddIncoming(call: ApplicationCall, httpClient: HttpClie val facadeState = getTalerFacadeState(facadeID) val facadeBankAccount = getTalerFacadeBankAccount(facadeID) return@transaction object { - val facadeLastSeen = facadeState.highestSeenMsgID + val facadeLastSeen = facadeState.highestSeenMessageSerialId val facadeIban = facadeBankAccount.iban val facadeBic = facadeBankAccount.bankCode val facadeHolderName = facadeBankAccount.accountHolder @@ -389,7 +394,7 @@ private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: Transact return } if (debtorAgent.bic == null) { - logger.warn("Not allowing transactions missing the BIC. IBAN and name: ${debtorIban}, ${debtorName}") + logger.warn("Not allowing transactions missing the BIC. IBAN and name: ${debtorIban}, $debtorName") return } TalerIncomingPaymentEntity.new { @@ -414,8 +419,8 @@ private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: Transact fun ingestTalerTransactions() { fun ingest(subscriberAccount: NexusBankAccountEntity, facade: FacadeEntity) { logger.debug("Ingesting transactions for Taler facade ${facade.id.value}") - val facadeState = getTalerFacadeState(facade.id.value) - var lastId = facadeState.highestSeenMsgID + val facadeState = getTalerFacadeState(facade.facadeName) + var lastId = facadeState.highestSeenMessageSerialId NexusBankTransactionEntity.find { /** Those with "our" bank account involved */ NexusBankTransactionsTable.bankAccount eq subscriberAccount.id.value and @@ -440,14 +445,14 @@ fun ingestTalerTransactions() { } lastId = it.id.value } - facadeState.highestSeenMsgID = lastId + facadeState.highestSeenMessageSerialId = lastId } // invoke ingestion for all the facades transaction { FacadeEntity.find { FacadesTable.type eq "taler-wire-gateway" }.forEach { - val subscriberAccount = getTalerFacadeBankAccount(it.id.value) + val subscriberAccount = getTalerFacadeBankAccount(it.facadeName) ingest(subscriberAccount, it) } } @@ -508,7 +513,7 @@ private suspend fun historyOutgoing(call: ApplicationCall) { /** * Handle a /taler-wire-gateway/history/incoming request. */ -private suspend fun historyIncoming(call: ApplicationCall): Unit { +private suspend fun historyIncoming(call: ApplicationCall) { val facadeId = expectNonNull(call.parameters["fcid"]) call.request.requirePermission(PermissionQuery("facade", facadeId, "facade.talerWireGateway.history")) val param = call.expectUrlParameter("delta") @@ -549,15 +554,9 @@ private suspend fun historyIncoming(call: ApplicationCall): Unit { } private fun getCurrency(facadeName: String): String { - val res = transaction { - TalerFacadeStateEntity.find { - TalerFacadeStateTable.facade eq facadeName - }.firstOrNull() - } ?: throw NexusError( - HttpStatusCode.InternalServerError, - "Facade '$facadeName' not found" - ) - return res.currency + return transaction { + getTalerFacadeState(facadeName).currency + } } fun talerFacadeRoutes(route: Route, httpClient: HttpClient) { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt @@ -45,7 +45,7 @@ fun requireBankAccount(call: ApplicationCall, parameterKey: String): NexusBankAc if (name == null) { throw NexusError(HttpStatusCode.InternalServerError, "no parameter for bank account") } - val account = transaction { NexusBankAccountEntity.findById(name) } + val account = transaction { NexusBankAccountEntity.findByName(name) } if (account == null) { throw NexusError(HttpStatusCode.NotFound, "bank connection '$name' not found") } @@ -83,6 +83,7 @@ suspend fun submitAllPaymentInitiations(httpClient: HttpClient, accountid: Strin logger.debug("auto-submitter started") val workQueue = mutableListOf<Submission>() transaction { + val account = NexusBankAccountEntity.findByName(accountid) PaymentInitiationEntity.find { PaymentInitiationsTable.submitted eq false }.forEach { @@ -113,8 +114,9 @@ private fun findDuplicate(bankAccountId: String, acctSvcrRef: String): NexusBank // FIXME: make this generic depending on transaction identification scheme val ati = "AcctSvcrRef:$acctSvcrRef" return transaction { + val account = NexusBankAccountEntity.findByName((bankAccountId)) ?: return@transaction null NexusBankTransactionEntity.find { - (NexusBankTransactionsTable.accountTransactionId eq ati) and (NexusBankTransactionsTable.bankAccount eq bankAccountId) + (NexusBankTransactionsTable.accountTransactionId eq ati) and (NexusBankTransactionsTable.bankAccount eq account.id) }.firstOrNull() } } @@ -123,16 +125,19 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): logger.info("processing CAMT message") var newTransactions = 0 transaction { - val acct = NexusBankAccountEntity.findById(bankAccountId) + val acct = NexusBankAccountEntity.findByName(bankAccountId) if (acct == null) { throw NexusError(HttpStatusCode.NotFound, "user not found") } - val res = try { parseCamtMessage(camtDoc) } catch (e: CamtParsingError) { + val res = try { + parseCamtMessage(camtDoc) + } catch (e: CamtParsingError) { logger.warn("Invalid CAMT received from bank: $e") newTransactions = -1 return@transaction } - val stamp = ZonedDateTime.parse(res.creationDateTime, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli() + val stamp = + ZonedDateTime.parse(res.creationDateTime, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli() when (code) { "C52" -> { val s = acct.lastReportCreationTimestamp @@ -207,18 +212,19 @@ fun processCamtMessage(bankAccountId: String, camtDoc: Document, code: String): fun ingestBankMessagesIntoAccount(bankConnectionId: String, bankAccountId: String): Int { var totalNew = 0 transaction { - val conn = NexusBankConnectionEntity.findById(bankConnectionId) + val conn = + NexusBankConnectionEntity.find { NexusBankConnectionsTable.connectionId eq bankConnectionId }.firstOrNull() if (conn == null) { throw NexusError(HttpStatusCode.InternalServerError, "connection not found") } - val acct = NexusBankAccountEntity.findById(bankAccountId) + val acct = NexusBankAccountEntity.findByName(bankAccountId) if (acct == null) { throw NexusError(HttpStatusCode.InternalServerError, "account not found") } - var lastId = acct.highestSeenBankMessageId + var lastId = acct.highestSeenBankMessageSerialId NexusBankMessageEntity.find { (NexusBankMessagesTable.bankConnection eq conn.id) and - (NexusBankMessagesTable.id greater acct.highestSeenBankMessageId) + (NexusBankMessagesTable.id greater acct.highestSeenBankMessageSerialId) }.orderBy(Pair(NexusBankMessagesTable.id, SortOrder.ASC)).forEach { val doc = XMLUtil.parseStringIntoDom(it.message.bytes.toString(Charsets.UTF_8)) val newTransactions = processCamtMessage(bankAccountId, doc, it.code) @@ -229,7 +235,7 @@ fun ingestBankMessagesIntoAccount(bankConnectionId: String, bankAccountId: Strin lastId = it.id.value totalNew += newTransactions } - acct.highestSeenBankMessageId = lastId + acct.highestSeenBankMessageSerialId = lastId } return totalNew } @@ -249,20 +255,20 @@ fun getPaymentInitiation(uuid: Long): PaymentInitiationEntity { /** * Insert one row in the database, and leaves it marked as non-submitted. - * @param debtorAccountId the mnemonic id assigned by the bank to one bank + * @param debtorAccount the mnemonic id assigned by the bank to one bank * account of the subscriber that is creating the pain entity. In this case, * it will be the account whose money will pay the wire transfer being defined * by this pain document. */ -fun addPaymentInitiation(paymentData: Pain001Data, debitorAccount: NexusBankAccountEntity): PaymentInitiationEntity { +fun addPaymentInitiation(paymentData: Pain001Data, debtorAccount: NexusBankAccountEntity): PaymentInitiationEntity { return transaction { val now = Instant.now().toEpochMilli() val nowHex = now.toString(16) - val painCounter = debitorAccount.pain001Counter++ + val painCounter = debtorAccount.pain001Counter++ val painHex = painCounter.toString(16) - val acctHex = debitorAccount.id.hashCode().toLong().toString(16).substring(0, 4) + val acctHex = debtorAccount.id.value.toString(16) PaymentInitiationEntity.new { - bankAccount = debitorAccount + bankAccount = debtorAccount subject = paymentData.subject sum = paymentData.sum creditorName = paymentData.creditorName @@ -279,7 +285,7 @@ fun addPaymentInitiation(paymentData: Pain001Data, debitorAccount: NexusBankAcco suspend fun fetchBankAccountTransactions(client: HttpClient, fetchSpec: FetchSpecJson, accountId: String): Int { val res = transaction { - val acct = NexusBankAccountEntity.findById(accountId) + val acct = NexusBankAccountEntity.findByName(accountId) if (acct == null) { throw NexusError( HttpStatusCode.NotFound, @@ -295,7 +301,7 @@ suspend fun fetchBankAccountTransactions(client: HttpClient, fetchSpec: FetchSpe } return@transaction object { val connectionType = conn.type - val connectionName = conn.id.value + val connectionName = conn.connectionId } } when (res.connectionType) { @@ -328,24 +334,25 @@ fun importBankAccount(call: ApplicationCall, offeredBankAccountId: String, nexus HttpStatusCode.NotFound, "Could not find offered bank account '${offeredBankAccountId}'" ) // detect name collisions first. - NexusBankAccountEntity.findById(nexusBankAccountId).run { - val importedAccount = when(this) { + NexusBankAccountEntity.findByName(nexusBankAccountId).run { + val importedAccount = when (this) { is NexusBankAccountEntity -> { if (this.iban != offeredAccount[OfferedBankAccountsTable.iban]) { throw NexusError( HttpStatusCode.Conflict, // different accounts == different IBANs - "Cannot import two different accounts under one label: ${nexusBankAccountId}" + "Cannot import two different accounts under one label: $nexusBankAccountId" ) } this } else -> { - val newImportedAccount = NexusBankAccountEntity.new(nexusBankAccountId) { + val newImportedAccount = NexusBankAccountEntity.new { + bankAccountName = nexusBankAccountId iban = offeredAccount[OfferedBankAccountsTable.iban] bankCode = offeredAccount[OfferedBankAccountsTable.bankCode] defaultBankConnection = conn - highestSeenBankMessageId = 0 + highestSeenBankMessageSerialId = 0 accountHolder = offeredAccount[OfferedBankAccountsTable.accountHolder] } logger.info("Account ${newImportedAccount.id} gets imported") @@ -353,8 +360,10 @@ fun importBankAccount(call: ApplicationCall, offeredBankAccountId: String, nexus } } OfferedBankAccountsTable.update( - {OfferedBankAccountsTable.offeredAccountId eq offeredBankAccountId and - (OfferedBankAccountsTable.bankConnection eq conn.id.value) } + { + OfferedBankAccountsTable.offeredAccountId eq offeredBankAccountId and + (OfferedBankAccountsTable.bankConnection eq conn.id.value) + } ) { it[imported] = importedAccount.id } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt @@ -78,7 +78,7 @@ suspend fun fetchEbicsBySpec( ) { val subscriberDetails = transaction { getEbicsSubscriberDetails(bankConnectionId) } val lastTimes = transaction { - val acct = NexusBankAccountEntity.findById(accountId) + val acct = NexusBankAccountEntity.findByName(accountId) if (acct == null) { throw NexusError( HttpStatusCode.NotFound, @@ -170,7 +170,7 @@ fun storeCamt(bankConnectionId: String, camt: String, historyType: String) { val msgId = camt53doc.pickStringWithRootNs("/*[1]/*[1]/root:GrpHdr/root:MsgId") logger.info("msg id $msgId") transaction { - val conn = NexusBankConnectionEntity.findById(bankConnectionId) + val conn = NexusBankConnectionEntity.findByName(bankConnectionId) if (conn == null) { throw NexusError(HttpStatusCode.InternalServerError, "bank connection missing") } @@ -239,7 +239,8 @@ fun createEbicsBankConnectionFromBackup( if (passphrase === null) { throw NexusError(HttpStatusCode.BadRequest, "EBICS backup needs passphrase") } - val bankConn = NexusBankConnectionEntity.new(bankConnectionName) { + val bankConn = NexusBankConnectionEntity.new { + connectionId = bankConnectionName owner = user type = "ebics" } @@ -323,7 +324,7 @@ private fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity) * Retrieve Ebics subscriber details given a bank connection. */ private fun getEbicsSubscriberDetails(bankConnectionId: String): EbicsClientSubscriberDetails { - val transport = NexusBankConnectionEntity.findById(bankConnectionId) + val transport = NexusBankConnectionEntity.findByName(bankConnectionId) if (transport == null) { throw NexusError(HttpStatusCode.NotFound, "transport not found") } @@ -352,16 +353,22 @@ suspend fun ebicsFetchAccounts(connId: String, client: HttpClient) { ) transaction { payload.value.partnerInfo.accountInfoList?.forEach { accountInfo -> + val conn = NexusBankConnectionEntity.findByName(connId) ?: throw NexusError( + HttpStatusCode.NotFound, + "bank connection not found" + ) + val isDuplicate = OfferedBankAccountsTable.select { - OfferedBankAccountsTable.bankConnection eq connId and ( + OfferedBankAccountsTable.bankConnection eq conn.id and ( OfferedBankAccountsTable.offeredAccountId eq accountInfo.id) }.firstOrNull() if (isDuplicate != null) return@forEach OfferedBankAccountsTable.insert { newRow -> newRow[accountHolder] = accountInfo.accountHolder ?: "NOT GIVEN" - newRow[iban] = accountInfo.accountNumberList?.filterIsInstance<EbicsTypes.GeneralAccountNumber>() - ?.find { it.international }?.value - ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no IBAN") + newRow[iban] = + accountInfo.accountNumberList?.filterIsInstance<EbicsTypes.GeneralAccountNumber>() + ?.find { it.international }?.value + ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no IBAN") newRow[bankCode] = accountInfo.bankCodeList?.filterIsInstance<EbicsTypes.GeneralBankCode>() ?.find { it.international }?.value ?: throw NexusError( @@ -397,7 +404,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { "bank connection is not of type 'ebics' (but '${conn.type}')" ) } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val resp = doEbicsIniRequest(client, subscriber) call.respond(resp) @@ -409,7 +416,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { if (conn.type != "ebics") { throw NexusError(HttpStatusCode.BadRequest, "bank connection is not of type 'ebics'") } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val resp = doEbicsHiaRequest(client, subscriber) call.respond(resp) @@ -421,7 +428,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { if (conn.type != "ebics") { throw NexusError(HttpStatusCode.BadRequest, "bank connection is not of type 'ebics'") } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val resp = doEbicsHostVersionQuery(client, subscriber.ebicsUrl, subscriber.hostId) call.respond(resp) @@ -433,7 +440,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { if (conn.type != "ebics") { throw NexusError(HttpStatusCode.BadRequest, "bank connection is not of type 'ebics'") } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val hpbData = doEbicsHpbRequest(client, subscriberDetails) transaction { @@ -456,7 +463,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { if (conn.type != "ebics") { throw NexusError(HttpStatusCode.BadRequest, "bank connection is not of type 'ebics'") } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val response = doEbicsDownloadTransaction( client, subscriberDetails, "HTD", EbicsStandardOrderParams() @@ -475,7 +482,8 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { transaction { val conn = requireBankConnection(call, "connid") payload.value.partnerInfo.accountInfoList?.forEach { - NexusBankAccountEntity.new(id = it.id) { + NexusBankAccountEntity.new { + bankAccountName = it.id accountHolder = it.accountHolder ?: "NOT-GIVEN" iban = it.accountNumberList?.filterIsInstance<EbicsTypes.GeneralAccountNumber>() ?.find { it.international }?.value @@ -487,7 +495,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { reason = "bank gave no BIC" ) defaultBankConnection = conn - highestSeenBankMessageId = 0 + highestSeenBankMessageSerialId = 0 } } } @@ -513,7 +521,7 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) { if (conn.type != "ebics") { throw NexusError(HttpStatusCode.BadRequest, "bank connection is not of type 'ebics'") } - getEbicsSubscriberDetails(conn.id.value) + getEbicsSubscriberDetails(conn.connectionId) } val response = doEbicsDownloadTransaction( client, @@ -575,7 +583,7 @@ fun exportEbicsKeyBackup(bankConnectionId: String, passphrase: String): Any { fun getEbicsConnectionDetails(conn: NexusBankConnectionEntity): Any { - val ebicsSubscriber = transaction { getEbicsSubscriberDetails(conn.id.value) } + val ebicsSubscriber = transaction { getEbicsSubscriberDetails(conn.connectionId) } val mapper = ObjectMapper() val details = mapper.createObjectNode() details.put("ebicsUrl", ebicsSubscriber.ebicsUrl) @@ -603,7 +611,7 @@ private suspend fun tentativeHpb(client: HttpClient, connId: String): Boolean { return false } transaction { - val conn = NexusBankConnectionEntity.findById(connId) + val conn = NexusBankConnectionEntity.findByName(connId) if (conn == null) { throw NexusError(HttpStatusCode.NotFound, "bank connection '$connId' not found") } @@ -649,7 +657,7 @@ suspend fun connectEbics(client: HttpClient, connId: String) { null } transaction { - val conn = NexusBankConnectionEntity.findById(connId) + val conn = NexusBankConnectionEntity.findByName(connId) if (conn == null) { throw NexusError(HttpStatusCode.NotFound, "bank connection '$connId' not found") } @@ -683,7 +691,7 @@ fun formatHex(ba: ByteArray): String { } fun getEbicsKeyLetterPdf(conn: NexusBankConnectionEntity): ByteArray { - val ebicsSubscriber = transaction { getEbicsSubscriberDetails(conn.id.value) } + val ebicsSubscriber = transaction { getEbicsSubscriberDetails(conn.connectionId) } val po = ByteArrayOutputStream() val pdfWriter = PdfWriter(po) @@ -752,9 +760,9 @@ suspend fun submitEbicsPaymentInitiation(httpClient: HttpClient, paymentInitiati val r = transaction { val paymentInitiation = PaymentInitiationEntity.findById(paymentInitiationId) ?: throw NexusError(HttpStatusCode.NotFound, "payment initiation not found") - val connId = paymentInitiation.bankAccount.defaultBankConnection?.id + val conn = paymentInitiation.bankAccount.defaultBankConnection ?: throw NexusError(HttpStatusCode.NotFound, "no default bank connection available for submission") - val subscriberDetails = getEbicsSubscriberDetails(connId.value) + val subscriberDetails = getEbicsSubscriberDetails(conn.connectionId) val painMessage = createPain001document( NexusPaymentInitiationData( debtorIban = paymentInitiation.bankAccount.iban, @@ -798,7 +806,8 @@ suspend fun submitEbicsPaymentInitiation(httpClient: HttpClient, paymentInitiati fun createEbicsBankConnection(bankConnectionName: String, user: NexusUserEntity, data: JsonNode) { - val bankConn = NexusBankConnectionEntity.new(bankConnectionName) { + val bankConn = NexusBankConnectionEntity.new { + this.connectionId = bankConnectionName owner = user type = "ebics" } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -28,31 +28,19 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import execThrowableOrTerminate -import io.ktor.application.ApplicationCall -import io.ktor.application.ApplicationCallPipeline -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.client.HttpClient -import io.ktor.features.CallLogging -import io.ktor.features.ContentNegotiation -import io.ktor.features.StatusPages -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.jackson.jackson +import io.ktor.application.* +import io.ktor.client.* +import io.ktor.features.* +import io.ktor.http.* +import io.ktor.jackson.* import io.ktor.request.* -import io.ktor.response.respond -import io.ktor.response.respondBytes -import io.ktor.response.respondText +import io.ktor.response.* import io.ktor.routing.* -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty +import io.ktor.server.engine.* +import io.ktor.server.netty.* import io.ktor.util.* import io.ktor.util.pipeline.* import io.ktor.utils.io.* -import io.ktor.utils.io.jvm.javaio.toByteReadChannel -import io.ktor.utils.io.jvm.javaio.toInputStream -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction @@ -67,10 +55,7 @@ import tech.libeufin.nexus.bankaccount.* import tech.libeufin.nexus.ebics.* import tech.libeufin.nexus.iso20022.CamtBankAccountEntry import tech.libeufin.util.* -import tech.libeufin.nexus.logger -import java.lang.IllegalArgumentException import java.net.URLEncoder -import java.util.zip.InflaterInputStream /** * Return facade state depending on the type. @@ -148,22 +133,26 @@ suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T { fun createLoopbackBankConnection(bankConnectionName: String, user: NexusUserEntity, data: JsonNode) { - val bankConn = NexusBankConnectionEntity.new(bankConnectionName) { + val bankConn = NexusBankConnectionEntity.new { + this.connectionId = bankConnectionName owner = user type = "loopback" } val bankAccount = jacksonObjectMapper().treeToValue(data, BankAccount::class.java) - NexusBankAccountEntity.new(bankAccount.nexusBankAccountId) { + NexusBankAccountEntity.new { + bankAccountName = bankAccount.nexusBankAccountId iban = bankAccount.iban bankCode = bankAccount.bic accountHolder = bankAccount.ownerName defaultBankConnection = bankConn - highestSeenBankMessageId = 0 + highestSeenBankMessageSerialId = 0 } } fun requireBankConnectionInternal(connId: String): NexusBankConnectionEntity { - return transaction { NexusBankConnectionEntity.findById(connId) } + return transaction { + NexusBankConnectionEntity.find { NexusBankConnectionsTable.connectionId eq connId }.firstOrNull() + } ?: throw NexusError(HttpStatusCode.NotFound, "bank connection '$connId' not found") } @@ -262,7 +251,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val ret = transaction { val currentUser = authenticateRequest(call.request) UserResponse( - username = currentUser.id.value, + username = currentUser.username, superuser = currentUser.superuser ) } @@ -325,7 +314,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val users = transaction { transaction { NexusUserEntity.all().map { - UserInfo(it.id.value, it.superuser) + UserInfo(it.username, it.superuser) } } } @@ -339,7 +328,8 @@ fun serverMain(dbName: String, host: String, port: Int) { val body = call.receiveJson<CreateUserRequest>() transaction { requireSuperuser(call.request) - NexusUserEntity.new(body.username) { + NexusUserEntity.new { + username = body.username passwordHash = CryptoUtil.hashpw(body.password) superuser = false } @@ -377,7 +367,7 @@ fun serverMain(dbName: String, host: String, port: Int) { ownerName = it.accountHolder, iban = it.iban, bic = it.bankCode, - nexusBankAccountId = it.id.value + nexusBankAccountId = it.bankAccountName ) ) } @@ -402,7 +392,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val accountId = ensureNonNull(call.parameters["accountid"]) resp.set<JsonNode>("schedule", ops) transaction { - NexusBankAccountEntity.findById(accountId) + NexusBankAccountEntity.findByName(accountId) ?: throw NexusError(HttpStatusCode.NotFound, "unknown bank account") NexusScheduledTaskEntity.find { (NexusScheduledTasksTable.resourceType eq "bank-account") and @@ -427,7 +417,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val accountId = ensureNonNull(call.parameters["accountid"]) transaction { authenticateRequest(call.request) - val bankAccount = NexusBankAccountEntity.findById(accountId) + val bankAccount = NexusBankAccountEntity.findByName(accountId) ?: throw NexusError(HttpStatusCode.NotFound, "unknown bank account") try { NexusCron.parser.parse(schedSpec.cronspec) @@ -500,7 +490,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val accountId = ensureNonNull(call.parameters["accountId"]) val taskId = ensureNonNull(call.parameters["taskId"]) transaction { - val bankAccount = NexusBankAccountEntity.findById(accountId) + val bankAccount = NexusBankAccountEntity.findByName(accountId) if (bankAccount == null) { throw NexusError(HttpStatusCode.NotFound, "unknown bank account") } @@ -520,7 +510,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val accountId = ensureNonNull(call.parameters["accountid"]) val res = transaction { val user = authenticateRequest(call.request) - val bankAccount = NexusBankAccountEntity.findById(accountId) + val bankAccount = NexusBankAccountEntity.findByName(accountId) if (bankAccount == null) { throw NexusError(HttpStatusCode.NotFound, "unknown bank account") } @@ -626,7 +616,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val accountId = ensureNonNull(call.parameters["accountid"]) val res = transaction { authenticateRequest(call.request) - val bankAccount = NexusBankAccountEntity.findById(accountId) + val bankAccount = NexusBankAccountEntity.findByName(accountId) if (bankAccount == null) { throw NexusError(HttpStatusCode.NotFound, "unknown bank account") } @@ -688,7 +678,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val ret = Transactions() transaction { authenticateRequest(call.request) - val bankAccount = NexusBankAccountEntity.findById(bankAccountId) + val bankAccount = NexusBankAccountEntity.findByName(bankAccountId) if (bankAccount == null) { throw NexusError(HttpStatusCode.NotFound, "unknown bank account") } @@ -710,7 +700,10 @@ fun serverMain(dbName: String, host: String, port: Int) { val body = call.receive<CreateBankConnectionRequestJson>() transaction { val user = authenticateRequest(call.request) - if (NexusBankConnectionEntity.findById(body.name) != null) { + val existingConn = + NexusBankConnectionEntity.find { NexusBankConnectionsTable.connectionId eq body.name } + .firstOrNull() + if (existingConn != null) { throw NexusError(HttpStatusCode.NotAcceptable, "connection '${body.name}' exists already") } when (body) { @@ -754,10 +747,12 @@ fun serverMain(dbName: String, host: String, port: Int) { requireSuperuser(call.request) val body = call.receive<BankConnectionDeletion>() transaction { - val conn = NexusBankConnectionEntity.findById(body.bankConnectionId) ?: throw NexusError( - HttpStatusCode.NotFound, - "Bank connection ${body.bankConnectionId}" - ) + val conn = + NexusBankConnectionEntity.find { NexusBankConnectionsTable.connectionId eq body.bankConnectionId } + .firstOrNull() ?: throw NexusError( + HttpStatusCode.NotFound, + "Bank connection ${body.bankConnectionId}" + ) conn.delete() // temporary, and instead just _mark_ it as deleted? } call.respond(object {}) @@ -770,7 +765,7 @@ fun serverMain(dbName: String, host: String, port: Int) { NexusBankConnectionEntity.all().forEach { connList.bankConnections.add( BankConnectionInfo( - name = it.id.value, + name = it.connectionId, type = it.type ) ) @@ -807,7 +802,7 @@ fun serverMain(dbName: String, host: String, port: Int) { val conn = requireBankConnection(call, "connid") when (conn.type) { "ebics" -> { - exportEbicsKeyBackup(conn.id.value, body.passphrase) + exportEbicsKeyBackup(conn.connectionId, body.passphrase) } else -> { throw NexusError( @@ -832,7 +827,7 @@ fun serverMain(dbName: String, host: String, port: Int) { } when (conn.type) { "ebics" -> { - connectEbics(client, conn.id.value) + connectEbics(client, conn.connectionId) } } call.respond(object {}) @@ -892,11 +887,11 @@ fun serverMain(dbName: String, host: String, port: Int) { requireSuperuser(call.request) val fcid = ensureNonNull(call.parameters["fcid"]) val ret = transaction { - val f = FacadeEntity.findById(fcid) ?: throw NexusError( + val f = FacadeEntity.findByName(fcid) ?: throw NexusError( HttpStatusCode.NotFound, "Facade ${fcid} does not exist" ) FacadeShowInfo( - name = f.id.value, + name = f.facadeName, type = f.type, baseUrl = "http://${host}/facades/${f.id.value}/${f.type}/", config = getFacadeState(f.type, f) @@ -918,7 +913,7 @@ fun serverMain(dbName: String, host: String, port: Int) { }.forEach { ret.facades.add( FacadeShowInfo( - name = it.id.value, + name = it.facadeName, type = it.type, baseUrl = "http://${host}/facades/${it.id.value}/${it.type}/", config = getFacadeState(it.type, it) @@ -939,7 +934,8 @@ fun serverMain(dbName: String, host: String, port: Int) { ) val newFacade = transaction { val user = authenticateRequest(call.request) - FacadeEntity.new(body.name) { + FacadeEntity.new { + facadeName = body.name type = body.type creator = user } @@ -971,7 +967,7 @@ fun serverMain(dbName: String, host: String, port: Int) { } when (conn.type) { "ebics" -> { - ebicsFetchAccounts(conn.id.value, client) + ebicsFetchAccounts(conn.connectionId, client) } else -> throw NexusError( HttpStatusCode.NotImplemented, @@ -987,16 +983,22 @@ fun serverMain(dbName: String, host: String, port: Int) { val ret = OfferedBankAccounts() transaction { val conn = requireBankConnection(call, "connid") - OfferedBankAccountsTable.select { + OfferedBankAccountEntity.find { OfferedBankAccountsTable.bankConnection eq conn.id.value - }.forEach { offeredAccountRow -> + }.forEach { offeredAccount -> + val importedId = offeredAccount.imported?.id + val imported = if (importedId != null) { + NexusBankAccountEntity.findById(importedId) + } else { + null + } ret.accounts.add( OfferedBankAccount( - ownerName = offeredAccountRow[accountHolder], - iban = offeredAccountRow[iban], - bic = offeredAccountRow[bankCode], - offeredAccountId = offeredAccountRow[offeredAccountId], - nexusBankAccountId = offeredAccountRow[imported]?.value + ownerName = offeredAccount.accountHolder, + iban = offeredAccount.iban, + bic = offeredAccount.bankCode, + offeredAccountId = offeredAccount.offeredAccountId, + nexusBankAccountId = imported?.bankAccountName ) ) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt @@ -1,3 +1,22 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2021 Taler Systems S.A. + * + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + * + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + package tech.libeufin.nexus.server import io.ktor.application.*