libeufin

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

commit 46d92a7f5212014266351aaf77e9f9b48e357e27
parent b6e9340fbd578a38d0245d7c8717aab6974f2735
Author: Marcello Stanisci <ms@taler.net>
Date:   Wed, 29 Apr 2020 16:14:28 +0200

Remove notion of "bank customer" from Sandbox.

Diffstat:
Dintegration-tests/test.py | 61-------------------------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 7++-----
Mnexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 22+++++++++++++++++++++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 35++++++++++++++++++++++++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 4++--
Msandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt | 99+++++++++++++------------------------------------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 48+++++++++++++++++++++++++++++++++---------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt | 128++++++++-----------------------------------------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 230+++++++++++++++++--------------------------------------------------------------
Dsandbox/src/test/kotlin/CamtGeneration.kt | 46----------------------------------------------
Dsandbox/src/test/kotlin/DbTest.kt | 138-------------------------------------------------------------------------------
Mutil/src/main/kotlin/ebics_h004/EbicsResponse.kt | 1-
12 files changed, 163 insertions(+), 656 deletions(-)

diff --git a/integration-tests/test.py b/integration-tests/test.py @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 - -from requests import post, get - -#1 Create a Nexus user -USERNAME="person" - -resp = post( - "http://localhost:5001/users/{}".format(USERNAME), - json=dict( - password="secret" - ) -) - -assert(resp.status_code == 200) - -#2 Create a EBICS user - -resp = post( - "http://localhost:5001/ebics/subscribers/{}".format(USERNAME), - json=dict( - ebicsURL="http://localhost:5000/ebicsweb", - hostID="HOST01", - partnerID="PARTNER1", - userID="USER1" - ) -) - -assert(resp.status_code == 200) - -#3 Upload keys to the bank INI & HIA -resp = post( - "http://localhost:5001/ebics/subscribers/{}/sendINI".format(USERNAME), - json=dict() -) - -assert(resp.status_code == 200) - -resp = post( - "http://localhost:5001/ebics/subscribers/{}/sendHIA".format(USERNAME), - json=dict() -) - -assert(resp.status_code == 200) - -#4 Download keys from the bank HPB -resp = post( - "http://localhost:5001/ebics/subscribers/{}/sync".format(USERNAME), - json=dict() -) - -assert(resp.status_code == 200) - -#5 Request history -#6 Prepare a payment -#7 Execute such payment via EBICS -#8 Request history again -#9 Exit - -# The Nexus should connect to the Sandbox for -# these tests! diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -5,9 +5,6 @@ import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction -import tech.libeufin.nexus.EbicsSubscribersTable.entityId -import tech.libeufin.nexus.EbicsSubscribersTable.nullable -import tech.libeufin.nexus.EbicsSubscribersTable.primaryKey import tech.libeufin.util.IntIdTableWithAmount import java.sql.Connection @@ -86,7 +83,7 @@ class TalerIncomingPaymentEntity(id: EntityID<Long>) : LongEntity(id) { * CAMT message. */ object RawBankTransactionsTable : LongIdTable() { - val nexusSubscriber = reference("subscriber", EbicsSubscribersTable) + val nexusUser = reference("nexusUser", NexusUsersTable) val sourceFileName = text("sourceFileName") /* ZIP entry's name */ val unstructuredRemittanceInformation = text("unstructuredRemittanceInformation") val transactionType = text("transactionType") /* DBIT or CRDT */ @@ -114,7 +111,7 @@ class RawBankTransactionEntity(id: EntityID<Long>) : LongEntity(id) { var creditorIban by RawBankTransactionsTable.creditorIban var counterpartBic by RawBankTransactionsTable.counterpartBic var bookingDate by RawBankTransactionsTable.bookingDate - var nexusSubscriber by EbicsSubscriberEntity referencedOn RawBankTransactionsTable.nexusSubscriber + var nexusUser by NexusUserEntity referencedOn RawBankTransactionsTable.nexusUser var status by RawBankTransactionsTable.status } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt @@ -138,8 +138,11 @@ class TestSubscriber() /** PAYMENT INSTRUCTIONS TYPES */ /** Represents a prepared payment at the nexus. This structure is - * used to SHOW a prepared payment to the called. */ + * used to SHOW a prepared payment to the caller. */ data class PaymentInfoElement( + /** + * This field is the mnemonic value that bank accounts usually have. + */ val debtorAccount: String, val creditorIban: String, val creditorBic: String, @@ -160,4 +163,21 @@ data class Pain001Data( val sum: Amount, val currency: String = "EUR", val subject: String +) + +/** + * This type is more of a debug one, and it shows details + * about one payment that was accounted by the bank (typically + * in a CAMT.05x entry) + */ +data class RawPayment( + val creditorIban: String, + val debitorIban: String, + val amount: String, + val subject: String, + val date: String +) + +data class RawPayments( + var payments: MutableList<RawPayment> = mutableListOf() ) \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -45,6 +45,7 @@ import kotlinx.coroutines.io.jvm.javaio.toInputStream import kotlinx.io.core.ExperimentalIoApi import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction +import org.joda.time.DateTime import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level @@ -285,6 +286,31 @@ fun main() { ) return@post } + get("/users/{id}/raw-payments") { + val nexusUser = extractNexusUser(call.parameters["id"]) + var ret = RawPayments() + transaction { + RawBankTransactionEntity.find { + RawBankTransactionsTable.nexusUser eq nexusUser.id.value + }.forEach { + ret.payments.add( + RawPayment( + creditorIban = it.creditorIban, + debitorIban = it.debitorIban, + subject = it.unstructuredRemittanceInformation, + date = DateTime(it.bookingDate).toDashedDate(), + amount = it.amount + " " + it.currency + ) + ) + } + } + call.respond( + HttpStatusCode.OK, + ret + ) + return@get + } + /** Associate a EBICS subscriber to the existing user */ post("/ebics/subscribers/{id}") { val nexusUser = extractNexusUser(call.parameters["id"]) @@ -587,10 +613,6 @@ fun main() { ) return@post } - post("/ebics/subscribers/{id}/collect-transactions-c52") { - // FIXME(florian): Download C52 and store the result in the right database table - - } /** exports keys backup copy */ post("/ebics/subscribers/{id}/backup") { val body = call.receive<EbicsBackupRequestJson>() @@ -628,7 +650,6 @@ fun main() { response ) } - /** Download keys from bank */ post("/ebics/subscribers/{id}/sync") { val nexusUser = extractNexusUser(call.parameters["id"]) @@ -696,13 +717,13 @@ fun main() { transaction { RawBankTransactionEntity.new { sourceFileName = fileName - unstructuredRemittanceInformation = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy") + unstructuredRemittanceInformation = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']") transactionType = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']") currency = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy") amount = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']") status = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']") bookingDate = parseDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis - nexusSubscriber = getSubscriberEntityFromNexusUserId(id) + nexusUser = extractNexusUser(id) creditorName = camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']") creditorIban = camt53doc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']") debitorName = camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='Dbtr']//*[local-name()='Nm']") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt @@ -282,7 +282,7 @@ class Taler(app: Route) { creditorIban = creditorObj.iban counterpartBic = creditorObj.bic bookingDate = DateTime.now().millis - nexusSubscriber = getEbicsSubscriberFromUser(nexusUser) + this.nexusUser = nexusUser status = "BOOK" } } else null @@ -338,7 +338,7 @@ class Taler(app: Route) { counterpartBic = debtor.bic bookingDate = DateTime.now().millis status = "BOOK" - nexusSubscriber = getSubscriberEntityFromNexusUserId(exchangeId) + nexusUser = extractNexusUser(exchangeId) } /** This payment is "valid by default" and will be returned * as soon as the exchange will ask for new payments. */ diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -19,27 +19,12 @@ package tech.libeufin.sandbox -import io.ktor.http.HttpStatusCode import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction -import tech.libeufin.util.IntIdTableWithAmount -import java.lang.ArithmeticException -import java.math.BigDecimal -import java.math.MathContext -import java.math.RoundingMode -import java.sql.Blob import java.sql.Connection -const val CUSTOMER_NAME_MAX_LENGTH = 20 -const val EBICS_HOST_ID_MAX_LENGTH = 10 -const val EBICS_USER_ID_MAX_LENGTH = 10 -const val EBICS_PARTNER_ID_MAX_LENGTH = 10 -const val EBICS_SYSTEM_ID_MAX_LENGTH = 10 -const val MAX_ID_LENGTH = 21 // enough to contain IBANs -const val MAX_SUBJECT_LENGTH = 140 // okay? - /** * All the states to give a subscriber. */ @@ -93,48 +78,6 @@ enum class KeyState { RELEASED } - -object BankTransactionsTable : IntIdTableWithAmount() { - /* Using varchar to store the IBAN - or possibly other formats - * - from the counterpart. */ - val counterpart = varchar("counterpart", MAX_ID_LENGTH) - val amount = amount("amount") - val subject = varchar("subject", MAX_SUBJECT_LENGTH) - val operationDate = long("operationDate") - val valueDate = long("valueDate") - val localCustomer = reference("localCustomer", BankCustomersTable) -} - -class BankTransactionEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<BankTransactionEntity>(BankTransactionsTable) - /* the id of the local customer involved in this transaction, - * either as the credit or the debit part; makes lookups easier */ - var localCustomer by BankCustomerEntity referencedOn BankTransactionsTable.localCustomer - /* keeping as strings, as to allow hosting IBANs and/or other - * unobvious formats. */ - var counterpart by BankTransactionsTable.counterpart - var subject by BankTransactionsTable.subject - var operationDate by BankTransactionsTable.operationDate - var valueDate by BankTransactionsTable.valueDate - var amount by BankTransactionsTable.amount -} - - -/** - * This table information *not* related to EBICS, for all - * its customers. - */ -object BankCustomersTable : IntIdTable() { - // Customer ID is the default 'id' field provided by the constructor. - val customerName = varchar("customerName", CUSTOMER_NAME_MAX_LENGTH) -} - -class BankCustomerEntity(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<BankCustomerEntity>(BankCustomersTable) - var customerName by BankCustomersTable.customerName -} - - /** * This table stores RSA public keys of subscribers. */ @@ -142,10 +85,6 @@ object EbicsSubscriberPublicKeysTable : IntIdTable() { val rsaPublicKey = blob("rsaPublicKey") val state = enumeration("state", KeyState::class) } - -/** - * Definition of a row in the [EbicsSubscriberPublicKeyEntity] table - */ class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsSubscriberPublicKeyEntity>(EbicsSubscriberPublicKeysTable) @@ -153,7 +92,9 @@ class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : IntEntity(id) { var state by EbicsSubscriberPublicKeysTable.state } - +/** + * Ebics 'host'(s) that are served by one Sandbox instance. + */ object EbicsHostsTable : IntIdTable() { val hostID = text("hostID") val ebicsVersion = text("ebicsVersion") @@ -161,11 +102,8 @@ 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 @@ -174,8 +112,7 @@ class EbicsHostEntity(id: EntityID<Int>) : IntEntity(id) { } /** - * Subscribers table. This table associates users with partners - * and systems. Each value can appear multiple times in the same column. + * Ebics Subscribers table. */ object EbicsSubscribersTable : IntIdTable() { val userId = text("userID") @@ -187,28 +124,23 @@ object EbicsSubscribersTable : IntIdTable() { val authenticationKey = reference("authorizationKey", EbicsSubscriberPublicKeysTable).nullable() val nextOrderID = integer("nextOrderID") val state = enumeration("state", SubscriberState::class) - val bankCustomer = reference("bankCustomer", BankCustomersTable) } - - 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 var hostId by EbicsSubscribersTable.hostId - var signatureKey by EbicsSubscriberPublicKeyEntity optionalReferencedOn EbicsSubscribersTable.signatureKey var encryptionKey by EbicsSubscriberPublicKeyEntity optionalReferencedOn EbicsSubscribersTable.encryptionKey var authenticationKey by EbicsSubscriberPublicKeyEntity optionalReferencedOn EbicsSubscribersTable.authenticationKey - var nextOrderID by EbicsSubscribersTable.nextOrderID var state by EbicsSubscribersTable.state - var bankCustomer by BankCustomerEntity referencedOn EbicsSubscribersTable.bankCustomer } - +/** + * Details of a download order. + */ object EbicsDownloadTransactionsTable : IdTable<String>() { override val id = text("transactionID").entityId() val orderType = text("orderType") @@ -220,7 +152,6 @@ 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 @@ -233,6 +164,9 @@ class EbicsDownloadTransactionEntity(id: EntityID<String>) : Entity<String>(id) var receiptReceived by EbicsDownloadTransactionsTable.receiptReceived } +/** + * Details of a upload order. + */ object EbicsUploadTransactionsTable : IdTable<String>() { override val id = text("transactionID").entityId() val orderType = text("orderType") @@ -243,10 +177,8 @@ 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 @@ -256,6 +188,9 @@ class EbicsUploadTransactionEntity(id: EntityID<String>) : Entity<String>(id) { var transactionKeyEnc by EbicsUploadTransactionsTable.transactionKeyEnc } +/** + * FIXME: document this. + */ object EbicsOrderSignaturesTable : IntIdTable() { val orderID = text("orderID") val orderType = text("orderType") @@ -264,7 +199,6 @@ 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 @@ -275,13 +209,15 @@ class EbicsOrderSignatureEntity(id: EntityID<Int>) : IntEntity(id) { var signatureValue by EbicsOrderSignaturesTable.signatureValue } +/** + * FIXME: document this. + */ object EbicsUploadTransactionChunksTable : IdTable<String>() { override val id = text("transactionID").entityId() 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 @@ -292,12 +228,9 @@ class EbicsUploadTransactionChunkEntity(id : EntityID<String>): Entity<String>(i fun dbCreateTables() { Database.connect("jdbc:sqlite:libeufin-sandbox.sqlite3", "org.sqlite.JDBC") TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE - transaction { addLogger(StdOutSqlLogger) SchemaUtils.create( - BankCustomersTable, - BankTransactionsTable, EbicsSubscribersTable, EbicsHostsTable, EbicsDownloadTransactionsTable, diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -140,7 +140,7 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement( respondText(text, ContentType.Application.Xml, HttpStatusCode.OK) } -fun buildCamtString(history: SizedIterable<BankTransactionEntity>, type: Int): String { +fun buildCamtString(type: Int): String { /** * Booking period: we keep two "lines" of booking periods; one for c52 and one for c53. @@ -409,22 +409,14 @@ fun buildCamtString(history: SizedIterable<BankTransactionEntity>, type: Int): S * @param history the list of all the history elements * @param type 52 or 53. */ -private fun constructCamtResponse(type: Int, customerId: Int, header: EbicsRequest.Header): String { - +private fun constructCamtResponse(type: Int, header: EbicsRequest.Header): String { val dateRange = (header.static.orderDetails?.orderParams as EbicsRequest.StandardOrderParams).dateRange val (start: org.joda.time.DateTime, end: org.joda.time.DateTime) = if (dateRange != null) { Pair(DateTime(dateRange.start.toGregorianCalendar().time), DateTime(dateRange.end.toGregorianCalendar().time)) } else Pair(DateTime(0), DateTime.now()) - val history = extractHistory( - customerId, - start, - end - ) - logger.debug("${history.count()} history elements found for account $customerId") - return buildCamtString(history, type) + return buildCamtString(type) } - private fun handleEbicsTSD(requestContext: RequestContext): ByteArray { return "Hello World".toByteArray() } @@ -433,11 +425,36 @@ private fun handleEbicsPTK(requestContext: RequestContext): ByteArray { return "Hello I am a dummy PTK response.".toByteArray() } +/** + * Process a payment request in the pain.001 format. + */ +private fun handleCct(paymentRequest: String, ebicsSubscriberEntity: EbicsSubscriberEntity) { + /** + * NOTE: this function is ONLY required to store some details + * to put then in the camt report. IBANs / amount / subject / names? + */ + val painDoc = XMLUtil.parseStringIntoDom(paymentRequest) + val creditorIban = painDoc.pickString("//*[local-name()='CdtrAcct']//*[local-name()='IBAN']") + val debitorIban = painDoc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']") + val subject = painDoc.pickString("//*[local-name()='Ustrd']") + val currency = painDoc.pickString("//*[local-name()='InstdAmt']/@ccy") + val amount = painDoc.pickString("//*[local-name()='InstdAmt']") + + /* + transaction { + PaymentEntity.new { + this.creditorIban = creditorIban + this.debitorIban = debitorIban + this.subject = subject + this.amount = "${currency}:${amount}" + } + }*/ +} + private fun handleEbicsC52(requestContext: RequestContext): ByteArray { val subscriber = requestContext.subscriber val camt = constructCamtResponse( 52, - subscriber.bankCustomer.id.value, requestContext.requestObject.header ) return camt.toByteArray().zip() @@ -447,7 +464,6 @@ private fun handleEbicsC53(requestContext: RequestContext): ByteArray { val subscriber = requestContext.subscriber val camt = constructCamtResponse( 53, - subscriber.bankCustomer.id.value, requestContext.requestObject.header ) return camt.toByteArray().zip() @@ -612,7 +628,6 @@ private suspend fun ApplicationCall.receiveEbicsXml(): Document { return requestDocument } - fun handleEbicsHtd(): ByteArray { val htd = HTDResponseOrderData().apply { this.partnerInfo = EbicsTypes.PartnerInfo().apply { @@ -909,7 +924,6 @@ private fun handleEbicsUploadTransactionInitialization(requestContext: RequestCo return EbicsResponse.createForUploadInitializationPhase(transactionID, orderID) } - private fun handleEbicsUploadTransactionTransmission(requestContext: RequestContext): EbicsResponse { val uploadTransaction = requestContext.uploadTransaction ?: throw EbicsInvalidRequestError() val requestObject = requestContext.requestObject @@ -952,6 +966,10 @@ private fun handleEbicsUploadTransactionTransmission(requestContext: RequestCont } } + /** Handling a payment request */ + if ("CCT" == requestContext.requestObject.header.static.orderDetails?.orderType) { + handleCct(unzippedData.toString(Charsets.UTF_8), requestContext.subscriber) + } return EbicsResponse.createForUploadTransferPhase( requestTransactionID, requestSegmentNumber, diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/JSON.kt @@ -19,122 +19,23 @@ package tech.libeufin.sandbox -/** - * Error message. - */ +/** Error message */ data class SandboxError( val message: String ) /** - * Request for POST /admin/customers - */ -data class CustomerRequest( - val name: String -) - -data class CustomerResponse( - val id: Int -) - -data class CustomerBalance( - val name: String, - val balance: String -) - -/** - * Response for GET /admin/customers/:id - */ -data class CustomerInfo( - val name: String, - val ebicsInfo: CustomerEbicsInfo -) - -data class CustomerEbicsInfo( - val userId: String -) - -data class CustomerHistoryRequest( - val start: String?, - val end: String? -) - -data class CustomerHistoryResponseElement( - var amount: String, - val subject: String, - val counterpart: String, - val operationDate: String, - val valueDate: String -) - -data class CustomerHistoryResponse( - var history: MutableList<CustomerHistoryResponseElement> = mutableListOf() -) - -/** - * Wrapper type around initialization letters - * for RSA keys. + * Used to show the list of Ebics hosts that exist + * in the system. */ -data class IniHiaLetters( - val ini: IniLetter, - val hia: HiaLetter -) - -/** - * Request for INI letter. - */ -data class IniLetter( - val userId: String, - val customerId: String, - val name: String, - val date: String, - val time: String, - val recipient: String, - val public_exponent_length: Int, - val public_exponent: String, - val public_modulus_length: Int, - val public_modulus: String, - val hash: String -) - -/** - * Request for HIA letter. - */ -data class HiaLetter( - val userId: String, - val customerId: String, - val name: String, - val date: String, - val time: String, - val recipient: String, - val ia_public_exponent_length: Int, - val ia_public_exponent: String, - val ia_public_modulus_length: Int, - val ia_public_modulus: String, - val ia_hash: String, - val enc_public_exponent_length: Int, - val enc_public_exponent: String, - val enc_public_modulus_length: Int, - val enc_public_modulus: String, - val enc_hash: String -) - -data class EbicsSubscribersResponse( - val subscribers: List<String> -) - -data class EbicsSubscriberResponse( - val id: String, - val partnerID: String, - val userID: String, - val systemID: String?, - val state: String -) - data class EbicsHostsResponse( val ebicsHosts: List<String> ) +/** + * Used to show information about ONE particular + * Ebics host that is active in the system. + */ data class EbicsHostResponse( val hostID: String, val ebicsVersion: String @@ -145,21 +46,16 @@ data class EbicsHostCreateRequest( val ebicsVersion: String ) -data class AdminAddSubscriberRequest( - val name: String, // person's name +/** + * Used to create AND show one Ebics subscriber in the system. + */ +data class EbicsSubscriberElement( val hostID: String, val partnerID: String, val userID: String, val systemID: String? = null ) -data class AdminSubscriberElement( - var name: String, - var userId: String, - var partnerID: String, - var hostID: String -) - data class AdminGetSubscribers( - var subscribers: MutableList<AdminSubscriberElement> = mutableListOf() + var subscribers: MutableList<EbicsSubscriberElement> = mutableListOf() ) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -60,20 +60,6 @@ class UnacceptableFractional(badNumber: BigDecimal) : Exception( "Unacceptable fractional part ${badNumber}" ) val LOGGER: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox") -fun findCustomer(id: String?): BankCustomerEntity { - - val idN = try { - id!!.toInt() - } catch (e: Exception) { - e.printStackTrace() - throw BadInputData(id) - } - - return transaction { - addLogger(StdOutSqlLogger) - BankCustomerEntity.findById(idN) ?: throw CustomerNotFound(id) - } -} fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?): EbicsSubscriberEntity? { return if (systemID == null) { @@ -120,82 +106,8 @@ fun BigDecimal.signToString(): String { // minus sign is added by default already. } -fun sampleData() { - transaction { - val pairA = CryptoUtil.generateRsaKeyPair(2048) - val pairB = CryptoUtil.generateRsaKeyPair(2048) - val pairC = CryptoUtil.generateRsaKeyPair(2048) - EbicsHostEntity.new { - hostId = "host01" - ebicsVersion = "H004" - authenticationPrivateKey = SerialBlob(pairA.private.encoded) - encryptionPrivateKey = SerialBlob(pairB.private.encoded) - signaturePrivateKey = SerialBlob(pairC.private.encoded) - } - val customerEntity = BankCustomerEntity.new { - addLogger(StdOutSqlLogger) - customerName = "Mina" - } - LOGGER.debug("Creating customer number: ${customerEntity.id}") - EbicsSubscriberEntity.new { - partnerId = "PARTNER1" - userId = "USER1" - systemId = null - hostId = "HOST01" - state = SubscriberState.NEW - nextOrderID = 1 - bankCustomer = customerEntity - } - for (i in listOf(Amount("-0.44"), Amount("6.02"))) { - BankTransactionEntity.new { - counterpart = "IBAN" - amount = i - subject = "transaction $i" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = customerEntity - } - } - } -} - -/** - * @param id the customer whose history must be returned. This - * id is local to the bank and is not reused/encoded into other - * EBICS id values. - * - * @return result set of all the operations related to the customer - * identified by @p id. - */ -fun extractHistory(id: Int, start: DateTime, end: DateTime): SizedIterable<BankTransactionEntity> { - LOGGER.debug("Fetching history from ${start.toLocalDateTime()} to ${end.toLocalDateTime()}") - return transaction { - addLogger(StdOutSqlLogger) - BankTransactionEntity.find { - BankTransactionsTable.localCustomer eq id and BankTransactionsTable.valueDate.between(start.millis, end.millis) - } - } -} - -fun calculateBalance(id: Int, start: String?, end: String?): BigDecimal { - val s = if (start != null) DateTime.parse(start) else DateTime(0) - val e = if (end != null) DateTime.parse(end) else DateTime.now() - - var ret = BigDecimal(0) - - transaction { - BankTransactionEntity.find { - BankTransactionsTable.localCustomer eq id and BankTransactionsTable.operationDate.between(s.millis, e.millis) - }.forEach { ret += it.amount } - } - return ret -} - fun main() { - dbCreateTables() - sampleData() - val server = embeddedServer(Netty, port = 5000) { install(CallLogging) { this.level = Level.DEBUG @@ -225,87 +137,65 @@ fun main() { } } routing { - post("/{id}/history") { - val req = call.receive<CustomerHistoryRequest>() - val customer = findCustomer(call.parameters["id"]) - val ret = CustomerHistoryResponse() - val history = extractHistory( - customer.id.value, - DateTime.parse(req.start ?: "1970-01-01"), - DateTime.parse(req.end ?: "3000-01-01") - ) + get("/") { + call.respondText("Hello Sandbox!\n", ContentType.Text.Plain) + } + /** EBICS ADMIN ENDPOINTS */ + + post("/admin/ebics-subscriber") { + val body = call.receive<EbicsSubscriberElement>() transaction { - history.forEach { - ret.history.add( - CustomerHistoryResponseElement( - subject = it.subject, - amount = "${it.amount.signToString()}${it.amount} EUR", - counterpart = it.counterpart, - operationDate = DateTime(it.operationDate).toString("Y-M-d"), - valueDate = DateTime(it.valueDate).toString("Y-M-d") - ) - ) + EbicsSubscriberEntity.new { + partnerId = body.partnerID + userId = body.userID + systemId = null + hostId = body.hostID + state = SubscriberState.NEW + nextOrderID = 1 } } - call.respond(ret) - return@post - } - get("/{id}/balance") { - val customer = findCustomer(call.parameters["id"]) - val balance = calculateBalance(customer.id.value, null, null) - call.respond( - CustomerBalance( - name = customer.customerName, - balance = "${balance} EUR" - ) + call.respondText( + "Subscriber created.", + ContentType.Text.Plain, HttpStatusCode.OK ) - return@get + return@post } - get("/admin/subscribers") { + get("/admin/ebics-subscribers") { var ret = AdminGetSubscribers() transaction { EbicsSubscriberEntity.all().forEach { ret.subscribers.add( - AdminSubscriberElement( - userId = it.userId, partnerID = it.partnerId, hostID = it.hostId, name = it.bankCustomer.customerName)) + EbicsSubscriberElement( + userID = it.userId, + partnerID = it.partnerId, + hostID = it.hostId + ) + ) } } call.respond(ret) return@get } - post("/admin/add/subscriber") { - val body = call.receive<AdminAddSubscriberRequest>() - transaction { - val customerEntity = BankCustomerEntity.new { - addLogger(StdOutSqlLogger) - customerName = body.name - } - EbicsSubscriberEntity.new { - partnerId = body.partnerID - userId = body.userID - systemId = null - hostId = body.hostID - state = SubscriberState.NEW - nextOrderID = 1 - bankCustomer = customerEntity - } + /* Show details about ONE Ebics host */ + get("/ebics/hosts/{id}") { + val resp = transaction { + val host = EbicsHostEntity.find { EbicsHostsTable.hostID eq call.parameters["id"]!! }.firstOrNull() + if (host == null) null + else EbicsHostResponse( + host.hostId, + host.ebicsVersion + ) } - - call.respondText("Subscriber created.", ContentType.Text.Plain, HttpStatusCode.OK) - return@post + if (resp == null) call.respond( + HttpStatusCode.NotFound, + SandboxError("host not found") + ) + else call.respond(resp) } - get("/") { - call.respondText("Hello LibEuFin!\n", ContentType.Text.Plain) - } - get("/ebics/hosts") { - val ebicsHosts = transaction { - EbicsHostEntity.all().map { it.hostId } - } - call.respond(EbicsHostsResponse(ebicsHosts)) - } + /** Create a new EBICS host. */ post("/ebics/hosts") { val req = call.receive<EbicsHostCreateRequest>() val pairA = CryptoUtil.generateRsaKeyPair(2048) @@ -323,47 +213,25 @@ fun main() { } } call.respondText( - "Host created.", + "Host '${req.hostId}' created.", ContentType.Text.Plain, HttpStatusCode.OK ) return@post } - get("/ebics/hosts/{id}") { - val resp = transaction { - val host = EbicsHostEntity.find { EbicsHostsTable.hostID eq call.parameters["id"]!! }.firstOrNull() - if (host == null) null - else EbicsHostResponse(host.hostId, host.ebicsVersion) - } - if (resp == null) call.respond( - HttpStatusCode.NotFound, - SandboxError("host not found") - ) - else call.respond(resp) - } - get("/ebics/subscribers") { - val subscribers = transaction { - EbicsSubscriberEntity.all().map { it.id.value.toString() } - } - call.respond(EbicsSubscribersResponse(subscribers)) - } - get("/ebics/subscribers/{id}") { - val resp = transaction { - val id = call.parameters["id"]!! - val subscriber = EbicsSubscriberEntity.findById(id.toInt())!! - EbicsSubscriberResponse( - id, - subscriber.partnerId, - subscriber.userId, - subscriber.systemId, - subscriber.state.name - ) + /* Show ONLY names of all the Ebics hosts */ + get("/ebics/hosts") { + val ebicsHosts = transaction { + EbicsHostEntity.all().map { it.hostId } } - call.respond(resp) + call.respond(EbicsHostsResponse(ebicsHosts)) } + + /** MAIN EBICS handler. */ post("/ebicsweb") { call.ebicsweb() } + } } LOGGER.info("Up and running") diff --git a/sandbox/src/test/kotlin/CamtGeneration.kt b/sandbox/src/test/kotlin/CamtGeneration.kt @@ -1,45 +0,0 @@ -package tech.libeufin.sandbox - -import org.jetbrains.exposed.sql.transactions.transaction -import org.joda.time.DateTime -import org.junit.Test -import org.junit.Before -import tech.libeufin.util.Amount -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.Database - -class CamtGeneration { - - @Before - fun connectAndMakeTables() { - Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") - transaction { - SchemaUtils.create(BankCustomersTable) - SchemaUtils.create(BankTransactionsTable) - } - } - - @Test - fun generateCamt() { - transaction { - val customer = BankCustomerEntity.new { - customerName = "payer" - } - for (iter in 1..5) { - BankTransactionEntity.new { - localCustomer = customer - counterpart = "IBAN${iter}" - subject = "subject #${iter}" - operationDate = DateTime.parse("3000-01-01").millis - valueDate = DateTime.parse("3000-01-02").millis - amount = Amount("${iter}.0") - } - } - val string = buildCamtString( - BankTransactionEntity.all(), - 53 - ) - println(string) - } - } -} -\ No newline at end of file diff --git a/sandbox/src/test/kotlin/DbTest.kt b/sandbox/src/test/kotlin/DbTest.kt @@ -1,137 +0,0 @@ -package tech.libeufin.sandbox - -import org.jetbrains.exposed.exceptions.ExposedSQLException -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.joda.time.DateTime -import org.junit.Before -import org.junit.Test -import java.io.ByteArrayOutputStream -import java.io.PrintStream -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode -import kotlin.math.abs -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue -import tech.libeufin.util.Amount -import tech.libeufin.util.BadAmount - - -class DbTest { - - @Before - fun muteStderr() { - System.setErr(PrintStream(ByteArrayOutputStream())) - } - - @Before - fun connectAndMakeTables() { - Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") - transaction { - SchemaUtils.create(BankTransactionsTable) - SchemaUtils.create(BankCustomersTable) - } - } - - @Test - fun customers() { - transaction { - BankCustomerEntity.new { - customerName = "Test Name" - } - findCustomer("1") - } - } - - - @Test - fun goodAmount() { - - transaction { - BankTransactionEntity.new { - amount = Amount("1") - counterpart = "IBAN" - subject = "Salary" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = BankCustomerEntity.new { - customerName = "employee" - } - } - - BankTransactionEntity.new { - amount = Amount("1.11") - counterpart = "IBAN" - subject = "Salary" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = BankCustomerEntity.new { - customerName = "employee" - } - } - - BankTransactionEntity.new { - amount = Amount("1.110000000000") // BigDecimal does not crop the trailing zeros - counterpart = "IBAN" - subject = "Salary" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = BankCustomerEntity.new { - customerName = "employee" - } - } - } - } - - @Test - fun badAmount() { - assertFailsWith<BadAmount> { - transaction { - BankTransactionEntity.new { - amount = Amount("1.10001") - counterpart = "IBAN" - subject = "Salary" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = BankCustomerEntity.new { - customerName = "employee" - } - } - } - } - } - - @Test - fun timeBasedQuery() { - - - val NQUERIES = 10 - - transaction { - - for (i in 1..NQUERIES) { - BankTransactionEntity.new { - amount = Amount("1") - counterpart = "IBAN" - subject = "Salary" - operationDate = DateTime.now().millis - valueDate = DateTime.now().millis - localCustomer = BankCustomerEntity.new { - customerName = "employee" - } - } - } - - assertEquals( - NQUERIES, - BankTransactionEntity.find { - BankTransactionsTable.valueDate.between(DateTime.parse("1970-01-01").millis, DateTime.parse("2999-12-31").millis) - }.count() - ) - } - } -} -\ No newline at end of file diff --git a/util/src/main/kotlin/ebics_h004/EbicsResponse.kt b/util/src/main/kotlin/ebics_h004/EbicsResponse.kt @@ -150,7 +150,6 @@ class EbicsResponse { } } - fun createForDownloadReceiptPhase(transactionID: String, positiveAck: Boolean): EbicsResponse { return EbicsResponse().apply { this.version = "H004"