libeufin

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

commit 4d2825a4c21f2c102425368e8faf6714cb5a5557
parent c4e54a7a4fd7cbea1a0524a505df5169c5fd1103
Author: Marcello Stanisci <ms@taler.net>
Date:   Mon, 27 Apr 2020 20:04:47 +0200

renaming

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 51+++++++++++++++++++++++++++++++++++++++++++--------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 26++++++++++++++++++++++----
Mnexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 345++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 32+++++++++++++++++++-------------
5 files changed, 258 insertions(+), 198 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -5,6 +5,9 @@ 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 @@ -161,9 +164,11 @@ class Pain001Entity(id: EntityID<Int>) : IntEntity(id) { var invalid by Pain001Table.invalid } +/** + * This table holds triples of <iban, bic, holder name>. + */ object BankAccountsTable : IdTable<String>() { override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() - val subscriber = reference("subscriber", EbicsSubscribersTable) val accountHolder = text("accountHolder").nullable() val iban = text("iban") val bankCode = text("bankCode") @@ -171,15 +176,12 @@ object BankAccountsTable : IdTable<String>() { class BankAccountEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, BankAccountEntity>(BankAccountsTable) - var subscriber by EbicsSubscriberEntity referencedOn BankAccountsTable.subscriber var accountHolder by BankAccountsTable.accountHolder var iban by BankAccountsTable.iban var bankCode by BankAccountsTable.bankCode } -object EbicsSubscribersTable : IdTable<String>() { - override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() - val password = blob("password").nullable() +object EbicsSubscribersTable : IntIdTable() { val ebicsURL = text("ebicsURL") val hostID = text("hostID") val partnerID = text("partnerID") @@ -192,9 +194,8 @@ object EbicsSubscribersTable : IdTable<String>() { val bankAuthenticationPublicKey = blob("bankAuthenticationPublicKey").nullable() } -class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) { - companion object : EntityClass<String, EbicsSubscriberEntity>(EbicsSubscribersTable) - var password by EbicsSubscribersTable.password +class EbicsSubscriberEntity(id: EntityID<Int>) : Entity<Int>(id) { + companion object : EntityClass<Int, EbicsSubscriberEntity>(EbicsSubscribersTable) var ebicsURL by EbicsSubscribersTable.ebicsURL var hostID by EbicsSubscribersTable.hostID var partnerID by EbicsSubscribersTable.partnerID @@ -207,6 +208,40 @@ class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) { var bankAuthenticationPublicKey by EbicsSubscribersTable.bankAuthenticationPublicKey } +object NexusUsersTable : IdTable<String>() { + override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() + val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable) + val password = EbicsSubscribersTable.blob("password").nullable() +} + +class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) { + companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable) + var ebicsSubscriber by EbicsSubscriberEntity referencedOn NexusUsersTable.ebicsSubscriber + var password by NexusUsersTable.password +} + +object UserToBankAccountsTable : IntIdTable() { + val nexusUser = reference("nexusUser", NexusUsersTable) + val bankAccount = reference("bankAccount", BankAccountsTable) +} + +class UserToBankAccountEntity(id: EntityID<Int>): IntEntity(id) { + companion object : IntEntityClass<UserToBankAccountEntity>(UserToBankAccountsTable) + var nexusUser by NexusUserEntity referencedOn UserToBankAccountsTable.nexusUser + var bankAccount by BankAccountEntity referencedOn EbicsToBankAccountsTable.bankAccount +} + +object EbicsToBankAccountsTable : IntIdTable() { + val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable) + val bankAccount = reference("bankAccount", BankAccountsTable) +} + +class EbicsToBankAccountEntity(id: EntityID<Int>): IntEntity(id) { + companion object : IntEntityClass<EbicsToBankAccountEntity>(EbicsToBankAccountsTable) + var ebicsSubscriber by EbicsSubscriberEntity referencedOn EbicsToBankAccountsTable.ebicsSubscriber + var bankAccount by BankAccountEntity referencedOn EbicsToBankAccountsTable.bankAccount +} + fun dbCreateTables() { Database.connect("jdbc:sqlite:libeufin-nexus.sqlite3", "org.sqlite.JDBC") TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt @@ -2,6 +2,7 @@ package tech.libeufin.nexus import io.ktor.application.ApplicationCall import io.ktor.http.HttpStatusCode +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction import org.joda.time.DateTime @@ -37,11 +38,11 @@ fun expectId(param: String?): String { } /* Needs a transaction{} block to be called */ -fun expectIdTransaction(param: String?): EbicsSubscriberEntity { +fun expectNexusIdTransaction(param: String?): NexusUserEntity { if (param == null) { throw NexusError(HttpStatusCode.BadRequest, "Null Id given") } - return EbicsSubscriberEntity.findById(param) ?: throw NexusError(HttpStatusCode.NotFound, "Subscriber: $param not found") + return NexusUserEntity.findById(param) ?: throw NexusError(HttpStatusCode.NotFound, "Subscriber: $param not found") } fun ApplicationCall.expectUrlParameter(name: String): String { @@ -110,13 +111,30 @@ fun authenticateRequest(authorization: String?): String { ) else authorization val subscriber = transaction { val (user, pass) = extractUserAndHashedPassword(headerLine) - EbicsSubscriberEntity.find { - EbicsSubscribersTable.id eq user and (EbicsSubscribersTable.password eq SerialBlob(pass)) + NexusUserEntity.find { + NexusUsersTable.id eq user and (NexusUsersTable.password eq SerialBlob(pass)) }.firstOrNull() } ?: throw NexusError(HttpStatusCode.Forbidden, "Wrong password") return subscriber.id.value } +/** + * Check if the subscriber has the right to use the (claimed) bank account. + * @param subscriber id of the EBICS subscriber to check + * @param bankAccount id of the claimed bank account + * @return true if the subscriber can use the bank account. + */ +fun subscriberHasRights(subscriber: EbicsSubscriberEntity, bankAccount: BankAccountEntity): Boolean { + val row = transaction { + EbicsToBankAccountEntity.find { + EbicsToBankAccountsTable.bankAccount eq bankAccount.id and + (EbicsToBankAccountsTable.ebicsSubscriber eq subscriber.id) + }.firstOrNull() + } + return row != null +} + + fun parseDate(date: String): DateTime { return DateTime.parse(date, DateTimeFormat.forPattern("YYYY-MM-DD")) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt @@ -78,7 +78,7 @@ data class EbicsSubscriberInfoRequestJson( * Contain the ID that identifies the new user in the Nexus system. */ data class EbicsSubscriberInfoResponseJson( - val accountID: String, + val nexusUserID: String, val ebicsURL: String, val hostID: String, val partnerID: String, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -68,24 +68,6 @@ import java.util.zip.InflaterInputStream import javax.crypto.EncryptedPrivateKeyInfo import javax.sql.rowset.serial.SerialBlob -fun testData() { - val pairA = CryptoUtil.generateRsaKeyPair(2048) - val pairB = CryptoUtil.generateRsaKeyPair(2048) - val pairC = CryptoUtil.generateRsaKeyPair(2048) - val salt = Random().nextLong() - transaction { - addLogger(StdOutSqlLogger) - EbicsSubscriberEntity.new(id = "default-customer-$salt") { - ebicsURL = "http://localhost:5000/ebicsweb" - userID = "USER1" - partnerID = "PARTNER1" - hostID = "host01" - signaturePrivateKey = SerialBlob(pairA.private.encoded) - encryptionPrivateKey = SerialBlob(pairB.private.encoded) - authenticationPrivateKey = SerialBlob(pairC.private.encoded) - } - } -} data class NexusError(val statusCode: HttpStatusCode, val reason: String) : Exception() @@ -95,11 +77,10 @@ fun isProduction(): Boolean { return System.getenv("NEXUS_PRODUCTION") != null } -fun getSubscriberEntityFromId(id: String): EbicsSubscriberEntity { +fun getSubscriberEntityFromNexusUserId(nexusUserId: String): EbicsSubscriberEntity { return transaction { - EbicsSubscriberEntity.findById(id) ?: throw NexusError( - HttpStatusCode.NotFound, "Subscriber not found from id '$id'" - ) + val nexusUser = expectNexusIdTransaction(nexusUserId) + nexusUser.ebicsSubscriber } } @@ -140,66 +121,74 @@ fun extractFirstBic(bankCodes: List<EbicsTypes.AbstractBankCode>?): String? { return null } +/** + * Get EBICS subscriber details from bank account id. + * bank account id => ... => ebics details + */ fun getSubscriberDetailsFromBankAccount(bankAccountId: String): EbicsClientSubscriberDetails { return transaction { - val accountInfo = BankAccountEntity.findById(bankAccountId) ?: throw NexusError(HttpStatusCode.NotFound, "Bank account ($bankAccountId) not managed by Nexus") - logger.debug("Mapping bank account: ${bankAccountId}, to customer: ${accountInfo.subscriber.id.value}") - getSubscriberDetailsFromId(accountInfo.subscriber.id.value) + val map = EbicsToBankAccountEntity.find { + EbicsToBankAccountsTable.bankAccount eq bankAccountId + }.firstOrNull() ?: throw NexusError( + HttpStatusCode.NotFound, + "Such bank account '$bankAccountId' has no EBICS subscriber associated" + ) + getSubscriberDetailsInternal(map.ebicsSubscriber) } } /** - * Given a subscriber id, returns the _list_ of bank accounts associated to it. + * Given a nexus user id, returns the _list_ of bank accounts associated to it. + * * @param id the subscriber id - * @return the query set containing the subscriber's bank accounts. The result - * is guaranteed not to be empty. + * @return the bank account associated with this user. Can/should be adapted to + * return multiple bank accounts. */ -fun getBankAccountsInfoFromId(id: String): SizedIterable<BankAccountEntity> { +fun getBankAccountFromNexusUserId(id: String): BankAccountEntity { logger.debug("Looking up bank account of user '$id'") - val list = transaction { - BankAccountEntity.find { - BankAccountsTable.subscriber eq id + val map = transaction { + UserToBankAccountEntity.find { + UserToBankAccountsTable.nexusUser eq id } + }.firstOrNull() ?: throw NexusError( + HttpStatusCode.NotFound, + "Such user '$id' does not have any bank account associated" + ) + return map.bankAccount +} + +fun getSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): EbicsClientSubscriberDetails { + var bankAuthPubValue: RSAPublicKey? = null + if (subscriber.bankAuthenticationPublicKey != null) { + bankAuthPubValue = CryptoUtil.loadRsaPublicKey( + subscriber.bankAuthenticationPublicKey?.toByteArray()!! + ) } - if (list.empty()) { - throw NexusError( - HttpStatusCode.NotFound, - "This subscriber '$id' did never fetch its own bank accounts, request HTD first." + var bankEncPubValue: RSAPublicKey? = null + if (subscriber.bankEncryptionPublicKey != null) { + bankEncPubValue = CryptoUtil.loadRsaPublicKey( + subscriber.bankEncryptionPublicKey?.toByteArray()!! ) } - return list -} + return EbicsClientSubscriberDetails( + bankAuthPub = bankAuthPubValue, + bankEncPub = bankEncPubValue, -fun getSubscriberDetailsFromId(id: String): EbicsClientSubscriberDetails { - return transaction { - val subscriber = EbicsSubscriberEntity.findById(id) ?: throw NexusError( - HttpStatusCode.NotFound, "subscriber not found from id '$id'" - ) - var bankAuthPubValue: RSAPublicKey? = null - if (subscriber.bankAuthenticationPublicKey != null) { - bankAuthPubValue = CryptoUtil.loadRsaPublicKey( - subscriber.bankAuthenticationPublicKey?.toByteArray()!! - ) - } - var bankEncPubValue: RSAPublicKey? = null - if (subscriber.bankEncryptionPublicKey != null) { - bankEncPubValue = CryptoUtil.loadRsaPublicKey( - subscriber.bankEncryptionPublicKey?.toByteArray()!! - ) - } - EbicsClientSubscriberDetails( - bankAuthPub = bankAuthPubValue, - bankEncPub = bankEncPubValue, + ebicsUrl = subscriber.ebicsURL, + hostId = subscriber.hostID, + userId = subscriber.userID, + partnerId = subscriber.partnerID, - ebicsUrl = subscriber.ebicsURL, - hostId = subscriber.hostID, - 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.toByteArray()), - customerAuthPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()), - customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()) - ) +fun getSubscriberDetailsFromNexusUserId(id: String): EbicsClientSubscriberDetails { + return transaction { + val nexusUser = expectNexusIdTransaction(id) + getSubscriberDetailsInternal(nexusUser.ebicsSubscriber) } } @@ -353,7 +342,6 @@ fun createPain001entity(entry: Pain001Data, debtorAccountId: String): Pain001Ent @KtorExperimentalAPI fun main() { dbCreateTables() - testData() val client = HttpClient() { expectSuccess = false // this way, it does not throw exceptions on != 200 responses. } @@ -433,7 +421,7 @@ fun main() { val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() println("PTK order params: $orderParams") - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "PTK", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -457,7 +445,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "HAC", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -481,7 +469,7 @@ fun main() { val ret = BankAccountsInfoResponse() transaction { BankAccountEntity.find { - BankAccountsTable.subscriber eq id + UserToBankAccountsTable.nexusUser eq id }.forEach { ret.accounts.add( BankAccountInfoElement( @@ -506,11 +494,11 @@ fun main() { post("/ebics/subscribers/{id}/accounts/{acctid}/prepare-payment") { val acctid = transaction { val accountInfo = expectAcctidTransaction(call.parameters["acctid"]) - val subscriber = expectIdTransaction(call.parameters["subscriber"]) - if (accountInfo.subscriber != subscriber) { + val nexusUser = expectNexusIdTransaction(call.parameters["id"]) + if (!subscriberHasRights(nexusUser.ebicsSubscriber, accountInfo)) { throw NexusError( HttpStatusCode.BadRequest, - "Claimed bank account '${accountInfo.id}' doesn't belong to subscriber '${subscriber.id}'!" + "Claimed bank account '${accountInfo.id}' doesn't belong to user '${nexusUser.id.value}'!" ) } accountInfo.id.value @@ -527,14 +515,16 @@ fun main() { * list all the prepared payments related to customer {id} */ get("/ebics/subscribers/{id}/payments") { - val id = expectId(call.parameters["id"]) + val nexusUserId = expectId(call.parameters["id"]) val ret = PaymentsInfo() transaction { - BankAccountEntity.find { - BankAccountsTable.subscriber eq id - }.forEach { + val nexusUser = expectNexusIdTransaction(nexusUserId) + val bankAccountsMap = EbicsToBankAccountEntity.find { + EbicsToBankAccountsTable.ebicsSubscriber eq nexusUser.ebicsSubscriber.id + } + bankAccountsMap.forEach { Pain001Entity.find { - Pain001Table.debtorAccount eq it.id.value + Pain001Table.debtorAccount eq it.bankAccount.iban }.forEach { ret.payments.add( PaymentInfoElement( @@ -632,7 +622,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction( client, subscriberData, @@ -662,7 +652,7 @@ fun main() { val id = expectId(call.parameters["id"]) var ret = "" transaction { - val subscriber: EbicsSubscriberEntity = getSubscriberEntityFromId(id) + val subscriber: EbicsSubscriberEntity = getSubscriberEntityFromNexusUserId(id) RawBankTransactionEntity.find { RawBankTransactionsTable.nexusSubscriber eq subscriber.id.value }.forEach { @@ -685,7 +675,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) when (val response = doEbicsDownloadTransaction(client, subscriberData, "C53", orderParams)) { is EbicsDownloadSuccessResult -> { /** @@ -707,7 +697,7 @@ fun main() { 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 = getSubscriberEntityFromId(id) + nexusSubscriber = getSubscriberEntityFromNexusUserId(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']") @@ -738,7 +728,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "C52", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -760,7 +750,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "CRZ", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -782,7 +772,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "C53", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -804,7 +794,7 @@ fun main() { val id = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "C54", orderParams) when (response) { is EbicsDownloadSuccessResult -> { @@ -825,7 +815,7 @@ fun main() { } get("/ebics/subscribers/{id}/sendHTD") { val customerIdAtNexus = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(customerIdAtNexus) + val subscriberData = getSubscriberDetailsFromNexusUserId(customerIdAtNexus) val response = doEbicsDownloadTransaction( client, subscriberData, @@ -851,7 +841,7 @@ fun main() { } post("/ebics/subscribers/{id}/sendHAA") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "HAA", EbicsStandardOrderParams()) when (response) { is EbicsDownloadSuccessResult -> { @@ -873,7 +863,7 @@ fun main() { post("/ebics/subscribers/{id}/sendHVZ") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) // FIXME: order params are wrong val response = doEbicsDownloadTransaction(client, subscriberData, "HVZ", EbicsStandardOrderParams()) when (response) { @@ -896,7 +886,7 @@ fun main() { post("/ebics/subscribers/{id}/sendHVU") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) // FIXME: order params are wrong val response = doEbicsDownloadTransaction(client, subscriberData, "HVU", EbicsStandardOrderParams()) when (response) { @@ -919,7 +909,7 @@ fun main() { post("/ebics/subscribers/{id}/sendHPD") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "HPD", EbicsStandardOrderParams()) when (response) { is EbicsDownloadSuccessResult -> { @@ -941,7 +931,7 @@ fun main() { get("/ebics/subscribers/{id}/sendHKD") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction( client, subscriberData, @@ -968,7 +958,7 @@ fun main() { post("/ebics/subscribers/{id}/sendTSD") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val response = doEbicsDownloadTransaction(client, subscriberData, "TSD", EbicsGenericOrderParams()) when (response) { is EbicsDownloadSuccessResult -> { @@ -989,7 +979,7 @@ fun main() { } get("/ebics/subscribers/{id}/keyletter") { - val id = expectId(call.parameters["id"]) + val nexusUserId = expectId(call.parameters["id"]) var usernameLine = "TODO" var recipientLine = "TODO" val customerIdLine = "TODO" @@ -1013,9 +1003,8 @@ fun main() { val timeLine = timeFormat.format(now) var hostID = "" transaction { - val subscriber = EbicsSubscriberEntity.findById(id) ?: throw NexusError( - HttpStatusCode.NotFound, "Subscriber '$id' not found" - ) + val nexusUser = expectNexusIdTransaction(nexusUserId) + val subscriber = nexusUser.ebicsSubscriber val signPubTmp = CryptoUtil.getRsaPublicFromPrivate( CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()) ) @@ -1117,19 +1106,21 @@ fun main() { HttpStatusCode.OK ) } - + /** + * Lists the EBICS subscribers known to this service. + */ get("/ebics/subscribers") { val ret = EbicsSubscribersResponseJson() transaction { - EbicsSubscriberEntity.all().forEach { + NexusUserEntity.all().forEach { ret.ebicsSubscribers.add( EbicsSubscriberInfoResponseJson( - accountID = it.id.value, - hostID = it.hostID, - partnerID = it.partnerID, - systemID = it.systemID, - ebicsURL = it.ebicsURL, - userID = it.userID + hostID = it.ebicsSubscriber.hostID, + partnerID = it.ebicsSubscriber.partnerID, + systemID = it.ebicsSubscriber.systemID, + ebicsURL = it.ebicsSubscriber.ebicsURL, + userID = it.ebicsSubscriber.userID, + nexusUserID = it.id.value ) ) } @@ -1138,19 +1129,20 @@ fun main() { return@get } + /** + * Get all the details associated with a NEXUS user. + */ get("/ebics/subscribers/{id}") { - val id = expectId(call.parameters["id"]) + val nexusUserId = expectId(call.parameters["id"]) val response = transaction { - val tmp = EbicsSubscriberEntity.findById(id) ?: throw NexusError( - HttpStatusCode.NotFound, "Subscriber '$id' not found" - ) + val nexusUser = expectNexusIdTransaction(nexusUserId) EbicsSubscriberInfoResponseJson( - accountID = tmp.id.value, - hostID = tmp.hostID, - partnerID = tmp.partnerID, - systemID = tmp.systemID, - ebicsURL = tmp.ebicsURL, - userID = tmp.userID + nexusUserID = nexusUser.id.value, + hostID = nexusUser.ebicsSubscriber.hostID, + partnerID = nexusUser.ebicsSubscriber.partnerID, + systemID = nexusUser.ebicsSubscriber.systemID, + ebicsURL = nexusUser.ebicsSubscriber.ebicsURL, + userID = nexusUser.ebicsSubscriber.userID ) } call.respond(HttpStatusCode.OK, response) @@ -1159,7 +1151,7 @@ fun main() { get("/ebics/{id}/sendHev") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val request = makeEbicsHEVRequest(subscriberData) val response = client.postToBank(subscriberData.ebicsUrl, request) val versionDetails = parseEbicsHEVResponse(response) @@ -1175,38 +1167,39 @@ fun main() { return@get } - post("/ebics/{id}/subscribers") { + /** + * Make a new NEXUS user in the system. This user gets (also) a new EBICS + * user associated. + */ + post("/{id}/subscribers") { val newUserId = call.parameters["id"] val body = call.receive<EbicsSubscriberInfoRequestJson>() val pairA = CryptoUtil.generateRsaKeyPair(2048) val pairB = CryptoUtil.generateRsaKeyPair(2048) val pairC = CryptoUtil.generateRsaKeyPair(2048) - val row = try { - transaction { - EbicsSubscriberEntity.new(id = expectId(newUserId)) { - ebicsURL = body.ebicsURL - hostID = body.hostID - partnerID = body.partnerID - userID = body.userID - systemID = body.systemID - signaturePrivateKey = SerialBlob(pairA.private.encoded) - encryptionPrivateKey = SerialBlob(pairB.private.encoded) - authenticationPrivateKey = SerialBlob(pairC.private.encoded) - password = if (body.password != null) { - SerialBlob(CryptoUtil.hashStringSHA256(body.password)) - } else { - logger.debug("No password set for $newUserId") - null - } + transaction { + val newEbicsSubscriber = EbicsSubscriberEntity.new { + ebicsURL = body.ebicsURL + hostID = body.hostID + partnerID = body.partnerID + userID = body.userID + systemID = body.systemID + signaturePrivateKey = SerialBlob(pairA.private.encoded) + encryptionPrivateKey = SerialBlob(pairB.private.encoded) + authenticationPrivateKey = SerialBlob(pairC.private.encoded) + } + NexusUserEntity.new(id = newUserId) { + ebicsSubscriber = newEbicsSubscriber + password = if (body.password != null) { + SerialBlob(CryptoUtil.hashStringSHA256(body.password)) + } else { + logger.debug("No password set for $newUserId") + null } } - } catch (e: Exception) { - print(e) - call.respond(NexusErrorJson("Could not store the new account into database")) - return@post } call.respondText( - "Subscriber registered, ID: ${row.id.value}", + "New NEXUS user registered. ID: $newUserId", ContentType.Text.Plain, HttpStatusCode.OK ) @@ -1215,7 +1208,7 @@ fun main() { post("/ebics/subscribers/{id}/sendIni") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val iniRequest = makeEbicsIniRequest(subscriberData) val responseStr = client.postToBank( subscriberData.ebicsUrl, @@ -1231,7 +1224,7 @@ fun main() { post("/ebics/subscribers/{id}/sendHia") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val hiaRequest = makeEbicsHiaRequest(subscriberData) val responseStr = client.postToBank( subscriberData.ebicsUrl, @@ -1251,9 +1244,9 @@ fun main() { post("/ebics/subscribers/{id}/restoreBackup") { val body = call.receive<EbicsKeysBackupJson>() - val id = expectId(call.parameters["id"]) + val nexusId = expectId(call.parameters["id"]) val subscriber = transaction { - EbicsSubscriberEntity.findById(id) + NexusUserEntity.findById(nexusId) } if (subscriber != null) { call.respond( @@ -1279,22 +1272,24 @@ fun main() { logger.info("Restoring keys failed, probably due to wrong passphrase") throw NexusError(HttpStatusCode.BadRequest, reason = "Bad backup given") } - logger.info("Restoring keys, creating new user: $id") + logger.info("Restoring keys, creating new user: $nexusId") try { transaction { - EbicsSubscriberEntity.new(id = expectId(call.parameters["id"])) { - ebicsURL = body.ebicsURL - hostID = body.hostID - partnerID = body.partnerID - userID = body.userID - signaturePrivateKey = SerialBlob(sigKey.encoded) - encryptionPrivateKey = SerialBlob(encKey.encoded) - authenticationPrivateKey = SerialBlob(authKey.encoded) + val newNexusUser = NexusUserEntity.new(id = nexusId) { + ebicsSubscriber = EbicsSubscriberEntity.new { + ebicsURL = body.ebicsURL + hostID = body.hostID + partnerID = body.partnerID + userID = body.userID + signaturePrivateKey = SerialBlob(sigKey.encoded) + encryptionPrivateKey = SerialBlob(encKey.encoded) + authenticationPrivateKey = SerialBlob(authKey.encoded) + } } } } catch (e: Exception) { print(e) - call.respond(NexusErrorJson("Could not store the new account $id into database")) + call.respond(NexusErrorJson("Could not store the new account into database")) return@post } call.respondText( @@ -1306,11 +1301,10 @@ fun main() { } get("/ebics/subscribers/{id}/pubkeys") { - val id = expectId(call.parameters["id"]) + val nexusId = expectId(call.parameters["id"]) val response = transaction { - val subscriber = EbicsSubscriberEntity.findById(id) ?: throw NexusError( - HttpStatusCode.NotFound, "Subscriber '$id' not found" - ) + val nexusUser = expectNexusIdTransaction(nexusId) + val subscriber = nexusUser.ebicsSubscriber val authPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()) val authPub = CryptoUtil.getRsaPublicFromPrivate(authPriv) val encPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()) @@ -1328,25 +1322,33 @@ fun main() { response ) } - + /** + * This endpoint downloads bank account information associated with the + * calling EBICS subscriber. + */ post("/ebics/subscribers/{id}/fetch-accounts") { - val customerIdAtNexus = expectId(call.parameters["id"]) + val nexusUserId = expectId(call.parameters["id"]) val paramsJson = call.receive<EbicsStandardOrderParamsJson>() val orderParams = paramsJson.toOrderParams() - val subscriberData = getSubscriberDetailsFromId(customerIdAtNexus) + val subscriberData = getSubscriberDetailsFromNexusUserId(nexusUserId) val response = doEbicsDownloadTransaction(client, subscriberData, "HTD", orderParams) when (response) { is EbicsDownloadSuccessResult -> { val payload = XMLUtil.convertStringToJaxb<HTDResponseOrderData>(response.orderData.toString(Charsets.UTF_8)) transaction { payload.value.partnerInfo.accountInfoList?.forEach { - BankAccountEntity.new(id = it.id) { - this.subscriber = getSubscriberEntityFromId(customerIdAtNexus) + val bankAccount = BankAccountEntity.new(id = it.id) { accountHolder = it.accountHolder iban = extractFirstIban(it.accountNumberList) ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no IBAN") bankCode = extractFirstBic(it.bankCodeList) ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no BIC") } + val nexusUser = expectNexusIdTransaction(nexusUserId) + EbicsToBankAccountEntity.new { + ebicsSubscriber = nexusUser.ebicsSubscriber + this.bankAccount = bankAccount + } } + } call.respondText( response.orderData.toString(Charsets.UTF_8), @@ -1366,10 +1368,11 @@ fun main() { /* performs a keys backup */ post("/ebics/subscribers/{id}/backup") { - val id = expectId(call.parameters["id"]) + val nexusId = expectId(call.parameters["id"]) val body = call.receive<EbicsBackupRequestJson>() val response = transaction { - val subscriber = EbicsSubscriberEntity.findById(id) ?: throw NexusError(HttpStatusCode.NotFound, "Subscriber '$id' not found") + val nexusUser = expectNexusIdTransaction(nexusId) + val subscriber = nexusUser.ebicsSubscriber EbicsKeysBackupJson( userID = subscriber.userID, hostID = subscriber.hostID, @@ -1404,9 +1407,8 @@ fun main() { post("/ebics/subscribers/{id}/sendTSU") { val id = expectId(call.parameters["id"]) - val subscriberData = getSubscriberDetailsFromId(id) + val subscriberData = getSubscriberDetailsFromNexusUserId(id) val payload = "PAYLOAD" - doEbicsUploadTransaction( client, subscriberData, @@ -1414,7 +1416,6 @@ fun main() { payload.toByteArray(Charsets.UTF_8), EbicsGenericOrderParams() ) - call.respondText( "TST INITIALIZATION & TRANSACTION phases succeeded\n", ContentType.Text.Plain, @@ -1423,8 +1424,8 @@ fun main() { } post("/ebics/subscribers/{id}/sync") { - val id = expectId(call.parameters["id"]) - val subscriberDetails = getSubscriberDetailsFromId(id) + val nexusId = expectId(call.parameters["id"]) + val subscriberDetails = getSubscriberDetailsFromNexusUserId(nexusId) val hpbRequest = makeEbicsHpbRequest(subscriberDetails) val responseStr = client.postToBank(subscriberDetails.ebicsUrl, hpbRequest) @@ -1435,9 +1436,9 @@ fun main() { // put bank's keys into database. transaction { - val subscriber = EbicsSubscriberEntity.findById(id) ?: throw NexusError(HttpStatusCode.BadRequest, "Invalid subscriber state") - subscriber.bankAuthenticationPublicKey = SerialBlob(hpbData.authenticationPubKey.encoded) - subscriber.bankEncryptionPublicKey = SerialBlob(hpbData.encryptionPubKey.encoded) + val nexusUser = expectNexusIdTransaction(nexusId) + nexusUser.ebicsSubscriber.bankAuthenticationPublicKey = SerialBlob(hpbData.authenticationPubKey.encoded) + nexusUser.ebicsSubscriber.bankEncryptionPublicKey = SerialBlob(hpbData.encryptionPubKey.encoded) } call.respondText("Bank keys stored in database\n", ContentType.Text.Plain, HttpStatusCode.OK) return@post diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt @@ -241,7 +241,8 @@ class Taler(app: Route) { val opaque_row_id = transaction { val creditorData = parsePayto(transferRequest.credit_account) - val exchangeBankAccount = getBankAccountsInfoFromId(exchangeId).first() + val exchangeBankAccount = getBankAccountFromNexusUserId(exchangeId) + val nexusUser = expectNexusIdTransaction(exchangeId) /** * Checking the UID has the desired characteristics. */ @@ -283,7 +284,7 @@ class Taler(app: Route) { creditorIban = creditorObj.iban counterpartBic = creditorObj.bic bookingDate = DateTime.now().millis - nexusSubscriber = exchangeBankAccount.subscriber + nexusSubscriber = nexusUser.ebicsSubscriber status = "BOOK" } } else null @@ -325,7 +326,7 @@ class Taler(app: Route) { val debtor = parsePayto(addIncomingData.debit_account) val amount = parseAmount(addIncomingData.amount) val (bookingDate, opaque_row_id) = transaction { - val exchangeBankAccount = getBankAccountsInfoFromId(exchangeId).first() + val exchangeBankAccount = getBankAccountFromNexusUserId(exchangeId) val rawPayment = RawBankTransactionEntity.new { sourceFileName = "test" unstructuredRemittanceInformation = addIncomingData.reserve_pub @@ -339,7 +340,7 @@ class Taler(app: Route) { counterpartBic = debtor.bic bookingDate = DateTime.now().millis status = "BOOK" - nexusSubscriber = getSubscriberEntityFromId(exchangeId) + nexusSubscriber = getSubscriberEntityFromNexusUserId(exchangeId) } /** This payment is "valid by default" and will be returned * as soon as the exchange will ask for new payments. */ @@ -369,9 +370,10 @@ class Taler(app: Route) { * all the prepared payments. */ app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") { transaction { - val subscriber = expectIdTransaction(call.parameters["id"]) + val nexusUser = expectNexusIdTransaction(call.parameters["id"]) + val subscriber = nexusUser.ebicsSubscriber val acctid = expectAcctidTransaction(call.parameters["acctid"]) - if (acctid.subscriber.id != subscriber.id) { + if (!subscriberHasRights(subscriber, acctid)) { throw NexusError( HttpStatusCode.Forbidden, "Such subscriber (${subscriber.id}) can't drive such account (${acctid.id})" @@ -406,7 +408,7 @@ class Taler(app: Route) { val id = expectId(call.parameters["id"]) // first find highest ID value of already processed rows. transaction { - val subscriberAccount = getBankAccountsInfoFromId(id).first() + val subscriberAccount = getBankAccountFromNexusUserId(id) /** * Search for fresh incoming payments in the raw table, and making pointers * from the Taler incoming payments table to the found fresh payments. @@ -493,7 +495,7 @@ class Taler(app: Route) { val history = TalerOutgoingHistory() transaction { /** Retrieve all the outgoing payments from the _clean Taler outgoing table_ */ - val subscriberBankAccount = getBankAccountsInfoFromId(subscriberId).first() + val subscriberBankAccount = getBankAccountFromNexusUserId(subscriberId) val reqPayments = TalerRequestedPaymentEntity.find { TalerRequestedPayments.rawConfirmed.isNotNull() and startCmpOp }.orderTaler(delta) @@ -536,18 +538,22 @@ class Taler(app: Route) { * bank - normally - but tests are currently avoiding any interaction * with banks or sandboxes. */ - if (! isProduction()) { + if (!isProduction()) { val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id" if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID) == null) { - BankAccountEntity.new(id = EXCHANGE_BANKACCOUNT_ID) { - subscriber = getSubscriberEntityFromId(exchangeId) + val newBankAccount = BankAccountEntity.new(id = EXCHANGE_BANKACCOUNT_ID) { accountHolder = "Test Exchange" iban = "42" bankCode = "localhost" } + val nexusUser = expectNexusIdTransaction(exchangeId) + EbicsToBankAccountEntity.new { + bankAccount = newBankAccount + ebicsSubscriber = nexusUser.ebicsSubscriber + } } } - val exchangeBankAccount = getBankAccountsInfoFromId(exchangeId) + val exchangeBankAccount = getBankAccountFromNexusUserId(exchangeId) val orderedPayments = TalerIncomingPaymentEntity.find { TalerIncomingPayments.valid eq true and startCmpOp }.orderTaler(delta) @@ -563,7 +569,7 @@ class Taler(app: Route) { it.payment.debitorName, it.payment.debitorIban, it.payment.counterpartBic ), credit_account = buildPaytoUri( - it.payment.creditorName, it.payment.creditorIban, exchangeBankAccount.first().bankCode + it.payment.creditorName, it.payment.creditorIban, exchangeBankAccount.bankCode ) ) )