gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: Continuing polymorphism


From: gnunet
Subject: [libeufin] branch master updated: Continuing polymorphism
Date: Mon, 03 May 2021 11:15:23 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 688af0d  Continuing polymorphism
688af0d is described below

commit 688af0d49e77ca918c7f66c37bd047b4560bf779
Author: MS <ms@taler.net>
AuthorDate: Mon May 3 11:15:20 2021 +0200

    Continuing polymorphism
---
 .../tech/libeufin/nexus/BankConnectionProtocol.kt  |  36 +
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt |  34 +-
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt | 749 ++++++++++-----------
 .../tech/libeufin/nexus/server/NexusServer.kt      |  70 +-
 4 files changed, 432 insertions(+), 457 deletions(-)

diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
index c54edc6..9cf5a43 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/BankConnectionProtocol.kt
@@ -19,9 +19,11 @@
 
 package tech.libeufin.nexus
 
+import com.fasterxml.jackson.databind.JsonNode
 import io.ktor.client.HttpClient
 import io.ktor.http.HttpStatusCode
 import tech.libeufin.nexus.ebics.*
+import tech.libeufin.nexus.server.FetchSpecJson
 
 // 'const' allows only primitive types.
 val bankConnectionRegistry: Map<String, BankConnectionProtocol> = mapOf(
@@ -29,7 +31,41 @@ val bankConnectionRegistry: Map<String, 
BankConnectionProtocol> = mapOf(
 )
 
 interface BankConnectionProtocol {
+    // Initialize the connection.  Usually uploads keys to the bank.
     suspend fun connect(client: HttpClient, connId: String)
+
+    // Downloads the list of bank accounts managed at the
+    // bank under one particular connection.
+    suspend fun fetchAccounts(client: HttpClient, connId: String)
+
+    // Create a new connection from backup data.
+    fun createConnectionFromBackup(connId: String, user: NexusUserEntity, 
passphrase: String?, backup: JsonNode)
+
+    // Create a new connection from a HTTP request.
+    fun createConnection(connId: String, user: NexusUserEntity, data: JsonNode)
+
+    // Merely a formatter of connection details coming from
+    // the database.
+    fun getConnectionDetails(conn: NexusBankConnectionEntity): JsonNode
+
+    // Returns the backup data.
+    fun exportBackup(bankConnectionId: String, passphrase: String): JsonNode
+
+    // Export a printable format of the connection details.  Useful
+    // to provide authentication via the traditional mail system.
+    fun exportAnalogDetails(conn: NexusBankConnectionEntity): ByteArray
+
+    // Send to the bank a previously prepared payment instruction.
+    suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long)
+
+    // Downlaods transactions from the bank, according to the specification
+    // given in the arguments.
+    suspend fun fetchTransactions(
+        fetchSpec: FetchSpecJson,
+        client: HttpClient,
+        bankConnectionId: String,
+        accountId: String
+    )
 }
 
 fun getConnectionPlugin(connId: String): BankConnectionProtocol {
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
index 5b872cc..ede581c 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -27,8 +27,6 @@ import org.jetbrains.exposed.sql.*
 import org.jetbrains.exposed.sql.transactions.transaction
 import org.w3c.dom.Document
 import tech.libeufin.nexus.*
-import tech.libeufin.nexus.ebics.fetchEbicsBySpec
-import tech.libeufin.nexus.ebics.submitEbicsPaymentInitiation
 import tech.libeufin.nexus.iso20022.CamtParsingError
 import tech.libeufin.nexus.iso20022.CreditDebitIndicator
 import tech.libeufin.nexus.iso20022.parseCamtMessage
@@ -52,7 +50,7 @@ fun requireBankAccount(call: ApplicationCall, parameterKey: 
String): NexusBankAc
     return account
 }
 
-
+// called twice: once from the background task, once from the ad-hoc endpoint.
 suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
     val r = transaction {
         val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
@@ -67,10 +65,10 @@ suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId:
     if (r.submitted) {
         return
     }
-    when (r.type) {
-        null -> throw NexusError(HttpStatusCode.NotFound, "no default bank 
connection")
-        "ebics" -> submitEbicsPaymentInitiation(httpClient, 
paymentInitiationId)
-    }
+    if (r.type == null)
+        throw NexusError(HttpStatusCode.NotFound, "no default bank connection")
+
+    getConnectionPlugin(r.type).submitPaymentInitiation(httpClient, 
paymentInitiationId)
 }
 
 /**
@@ -308,20 +306,14 @@ suspend fun fetchBankAccountTransactions(client: 
HttpClient, fetchSpec: FetchSpe
             val connectionName = conn.connectionId
         }
     }
-    when (res.connectionType) {
-        "ebics" -> {
-            fetchEbicsBySpec(
-                fetchSpec,
-                client,
-                res.connectionName,
-                accountId
-            )
-        }
-        else -> throw NexusError(
-            HttpStatusCode.BadRequest,
-            "Connection type '${res.connectionType}' not implemented"
-        )
-    }
+
+    getConnectionPlugin(res.connectionType).fetchTransactions(
+        fetchSpec,
+        client,
+        res.connectionName,
+        accountId
+    )
+
     val newTransactions = ingestBankMessagesIntoAccount(res.connectionName, 
accountId)
     ingestTalerTransactions(accountId)
     return newTransactions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index 2bd74ca..18f5848 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -70,101 +70,7 @@ private data class EbicsFetchSpec(
     val orderParams: EbicsOrderParams
 )
 
-suspend fun fetchEbicsBySpec(
-    fetchSpec: FetchSpecJson,
-    client: HttpClient,
-    bankConnectionId: String,
-    accountId: String
-) {
-    val subscriberDetails = transaction { 
getEbicsSubscriberDetails(bankConnectionId) }
-    val lastTimes = transaction {
-        val acct = NexusBankAccountEntity.findByName(accountId)
-        if (acct == null) {
-            throw NexusError(
-                HttpStatusCode.NotFound,
-                "Account not found"
-            )
-        }
-        object {
-            val lastStatement = acct.lastStatementCreationTimestamp?.let {
-                ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), 
ZoneOffset.UTC)
-            }
-            val lastReport = acct.lastReportCreationTimestamp?.let {
-                ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), 
ZoneOffset.UTC)
-            }
-        }
-    }
-    val specs = mutableListOf<EbicsFetchSpec>()
-
-    fun addForLevel(l: FetchLevel, p: EbicsOrderParams) {
-        when (l) {
-            FetchLevel.ALL -> {
-                specs.add(EbicsFetchSpec("C52", p))
-                specs.add(EbicsFetchSpec("C53", p))
-            }
-            FetchLevel.REPORT -> {
-                specs.add(EbicsFetchSpec("C52", p))
-            }
-            FetchLevel.STATEMENT -> {
-                specs.add(EbicsFetchSpec("C53", p))
-            }
-        }
-    }
-
-    when (fetchSpec) {
-        is FetchSpecLatestJson -> {
-            val p = EbicsStandardOrderParams()
-            addForLevel(fetchSpec.level, p)
-        }
-        is FetchSpecAllJson -> {
-            val p = EbicsStandardOrderParams(
-                EbicsDateRange(
-                    ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
-                    ZonedDateTime.now(ZoneOffset.UTC)
-                )
-            )
-            addForLevel(fetchSpec.level, p)
-        }
-        is FetchSpecSinceLastJson -> {
-            val pRep = EbicsStandardOrderParams(
-                EbicsDateRange(
-                    lastTimes.lastReport ?: ZonedDateTime.ofInstant(
-                        Instant.EPOCH,
-                        ZoneOffset.UTC
-                    ), ZonedDateTime.now(ZoneOffset.UTC)
-                )
-            )
-            val pStmt = EbicsStandardOrderParams(
-                EbicsDateRange(
-                    lastTimes.lastStatement ?: ZonedDateTime.ofInstant(
-                        Instant.EPOCH,
-                        ZoneOffset.UTC
-                    ), ZonedDateTime.now(ZoneOffset.UTC)
-                )
-            )
-            when (fetchSpec.level) {
-                FetchLevel.ALL -> {
-                    specs.add(EbicsFetchSpec("C52", pRep))
-                    specs.add(EbicsFetchSpec("C53", pStmt))
-                }
-                FetchLevel.REPORT -> {
-                    specs.add(EbicsFetchSpec("C52", pRep))
-                }
-                FetchLevel.STATEMENT -> {
-                    specs.add(EbicsFetchSpec("C53", pStmt))
-                }
-            }
-        }
-    }
-    for (spec in specs) {
-        try {
-            fetchEbicsC5x(spec.orderType, client, bankConnectionId, 
spec.orderParams, subscriberDetails)
-        } catch (e: Exception) {
-            logger.warn("Ingestion failed for $spec")
-        }
-    }
-}
-
+// Moved eventually in a tucked "camt" file.
 fun storeCamt(bankConnectionId: String, camt: String, historyType: String) {
     val camt53doc = XMLUtil.parseStringIntoDom(camt)
     val msgId = 
camt53doc.pickStringWithRootNs("/*[1]/*[1]/root:GrpHdr/root:MsgId")
@@ -229,67 +135,6 @@ private suspend fun fetchEbicsC5x(
     }
 }
 
-
-fun createEbicsBankConnectionFromBackup(
-    bankConnectionName: String,
-    user: NexusUserEntity,
-    passphrase: String?,
-    backup: JsonNode
-) {
-    if (passphrase === null) {
-        throw NexusError(HttpStatusCode.BadRequest, "EBICS backup needs 
passphrase")
-    }
-    val bankConn = NexusBankConnectionEntity.new {
-        connectionId = bankConnectionName
-        owner = user
-        type = "ebics"
-    }
-    val ebicsBackup = jacksonObjectMapper().treeToValue(backup, 
EbicsKeysBackupJson::class.java)
-    val (authKey, encKey, sigKey) = try {
-        Triple(
-            CryptoUtil.decryptKey(
-                EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.authBlob)),
-                passphrase
-            ),
-            CryptoUtil.decryptKey(
-                EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.encBlob)),
-                passphrase
-            ),
-            CryptoUtil.decryptKey(
-                EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.sigBlob)),
-                passphrase
-            )
-        )
-    } catch (e: Exception) {
-        e.printStackTrace()
-        logger.info("Restoring keys failed, probably due to wrong passphrase")
-        throw NexusError(
-            HttpStatusCode.BadRequest,
-            "Bad backup given"
-        )
-    }
-    try {
-        EbicsSubscriberEntity.new {
-            ebicsURL = ebicsBackup.ebicsURL
-            hostID = ebicsBackup.hostID
-            partnerID = ebicsBackup.partnerID
-            userID = ebicsBackup.userID
-            signaturePrivateKey = ExposedBlob(sigKey.encoded)
-            encryptionPrivateKey = ExposedBlob((encKey.encoded))
-            authenticationPrivateKey = ExposedBlob((authKey.encoded))
-            nexusBankConnection = bankConn
-            ebicsIniState = EbicsInitState.UNKNOWN
-            ebicsHiaState = EbicsInitState.UNKNOWN
-        }
-    } catch (e: Exception) {
-        throw NexusError(
-            HttpStatusCode.BadRequest,
-            "exception: $e"
-        )
-    }
-    return
-}
-
 private fun getEbicsSubscriberDetailsInternal(subscriber: 
EbicsSubscriberEntity): EbicsClientSubscriberDetails {
     var bankAuthPubValue: RSAPublicKey? = null
     if (subscriber.bankAuthenticationPublicKey != null) {
@@ -335,55 +180,6 @@ private fun getEbicsSubscriberDetails(bankConnectionId: 
String): EbicsClientSubs
     return getEbicsSubscriberDetailsInternal(subscriber)
 }
 
-suspend fun ebicsFetchAccounts(connId: String, client: HttpClient) {
-    val subscriberDetails = transaction { getEbicsSubscriberDetails(connId) }
-    val response = doEbicsDownloadTransaction(
-        client, subscriberDetails, "HTD", EbicsStandardOrderParams()
-    )
-    when (response) {
-        is EbicsDownloadBankErrorResult -> {
-            throw NexusError(
-                HttpStatusCode.BadGateway,
-                response.returnCode.errorCode
-            )
-        }
-        is EbicsDownloadSuccessResult -> {
-            val payload = XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
-                response.orderData.toString(Charsets.UTF_8)
-            )
-            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 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[bankCode] = 
accountInfo.bankCodeList?.filterIsInstance<EbicsTypes.GeneralBankCode>()
-                            ?.find { it.international }?.value
-                            ?: throw NexusError(
-                                HttpStatusCode.NotFound,
-                                reason = "bank gave no BIC"
-                            )
-                        newRow[bankConnection] = 
requireBankConnectionInternal(connId).id
-                        newRow[offeredAccountId] = accountInfo.id
-                    }
-                }
-            }
-        }
-    }
-}
-
 fun Route.ebicsBankProtocolRoutes(client: HttpClient) {
     post("test-host") {
         val r = call.receiveJson<EbicsHostTestRequest>()
@@ -552,52 +348,6 @@ fun Route.ebicsBankConnectionRoutes(client: HttpClient) {
     }
 }
 
-fun exportEbicsKeyBackup(bankConnectionId: String, passphrase: String): Any {
-    val subscriber = transaction { getEbicsSubscriberDetails(bankConnectionId) 
}
-    return EbicsKeysBackupJson(
-        type = "ebics",
-        userID = subscriber.userId,
-        hostID = subscriber.hostId,
-        partnerID = subscriber.partnerId,
-        ebicsURL = subscriber.ebicsUrl,
-        authBlob = bytesToBase64(
-            CryptoUtil.encryptKey(
-                subscriber.customerAuthPriv.encoded,
-                passphrase
-            )
-        ),
-        encBlob = bytesToBase64(
-            CryptoUtil.encryptKey(
-                subscriber.customerEncPriv.encoded,
-                passphrase
-            )
-        ),
-        sigBlob = bytesToBase64(
-            CryptoUtil.encryptKey(
-                subscriber.customerSignPriv.encoded,
-                passphrase
-            )
-        )
-    )
-}
-
-
-fun getEbicsConnectionDetails(conn: NexusBankConnectionEntity): Any {
-    val ebicsSubscriber = transaction { 
getEbicsSubscriberDetails(conn.connectionId) }
-    val mapper = ObjectMapper()
-    val details = mapper.createObjectNode()
-    details.put("ebicsUrl", ebicsSubscriber.ebicsUrl)
-    details.put("ebicsHostId", ebicsSubscriber.hostId)
-    details.put("partnerId", ebicsSubscriber.partnerId)
-    details.put("userId", ebicsSubscriber.userId)
-    val node = mapper.createObjectNode()
-    node.put("type", conn.type)
-    node.put("owner", conn.owner.username)
-    node.put("ready", true) // test with #6715 needed.
-    node.set<JsonNode>("details", details)
-    return node
-}
-
 /**
  * Do the Hpb request when we don't know whether our keys have been submitted 
or not.
  *
@@ -640,19 +390,163 @@ fun formatHex(ba: ByteArray): String {
     return out
 }
 
-fun getEbicsKeyLetterPdf(conn: NexusBankConnectionEntity): ByteArray {
-    val ebicsSubscriber = transaction { 
getEbicsSubscriberDetails(conn.connectionId) }
+class EbicsBankConnectionProtocol: BankConnectionProtocol {
 
-    val po = ByteArrayOutputStream()
-    val pdfWriter = PdfWriter(po)
-    val pdfDoc = PdfDocument(pdfWriter)
-    val date = LocalDateTime.now()
-    val dateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
+    override suspend fun fetchTransactions(
+        fetchSpec: FetchSpecJson,
+        client: HttpClient,
+        bankConnectionId: String,
+        accountId: String
+    ) {
+        val subscriberDetails = transaction { 
getEbicsSubscriberDetails(bankConnectionId) }
+        val lastTimes = transaction {
+            val acct = NexusBankAccountEntity.findByName(accountId)
+            if (acct == null) {
+                throw NexusError(
+                    HttpStatusCode.NotFound,
+                    "Account not found"
+                )
+            }
+            object {
+                val lastStatement = acct.lastStatementCreationTimestamp?.let {
+                    ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), 
ZoneOffset.UTC)
+                }
+                val lastReport = acct.lastReportCreationTimestamp?.let {
+                    ZonedDateTime.ofInstant(Instant.ofEpochMilli(it), 
ZoneOffset.UTC)
+                }
+            }
+        }
+        val specs = mutableListOf<EbicsFetchSpec>()
 
-    fun writeCommon(doc: Document) {
-        doc.add(
-            Paragraph(
-                """
+        fun addForLevel(l: FetchLevel, p: EbicsOrderParams) {
+            when (l) {
+                FetchLevel.ALL -> {
+                    specs.add(EbicsFetchSpec("C52", p))
+                    specs.add(EbicsFetchSpec("C53", p))
+                }
+                FetchLevel.REPORT -> {
+                    specs.add(EbicsFetchSpec("C52", p))
+                }
+                FetchLevel.STATEMENT -> {
+                    specs.add(EbicsFetchSpec("C53", p))
+                }
+            }
+        }
+
+        when (fetchSpec) {
+            is FetchSpecLatestJson -> {
+                val p = EbicsStandardOrderParams()
+                addForLevel(fetchSpec.level, p)
+            }
+            is FetchSpecAllJson -> {
+                val p = EbicsStandardOrderParams(
+                    EbicsDateRange(
+                        ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
+                        ZonedDateTime.now(ZoneOffset.UTC)
+                    )
+                )
+                addForLevel(fetchSpec.level, p)
+            }
+            is FetchSpecSinceLastJson -> {
+                val pRep = EbicsStandardOrderParams(
+                    EbicsDateRange(
+                        lastTimes.lastReport ?: ZonedDateTime.ofInstant(
+                            Instant.EPOCH,
+                            ZoneOffset.UTC
+                        ), ZonedDateTime.now(ZoneOffset.UTC)
+                    )
+                )
+                val pStmt = EbicsStandardOrderParams(
+                    EbicsDateRange(
+                        lastTimes.lastStatement ?: ZonedDateTime.ofInstant(
+                            Instant.EPOCH,
+                            ZoneOffset.UTC
+                        ), ZonedDateTime.now(ZoneOffset.UTC)
+                    )
+                )
+                when (fetchSpec.level) {
+                    FetchLevel.ALL -> {
+                        specs.add(EbicsFetchSpec("C52", pRep))
+                        specs.add(EbicsFetchSpec("C53", pStmt))
+                    }
+                    FetchLevel.REPORT -> {
+                        specs.add(EbicsFetchSpec("C52", pRep))
+                    }
+                    FetchLevel.STATEMENT -> {
+                        specs.add(EbicsFetchSpec("C53", pStmt))
+                    }
+                }
+            }
+        }
+        for (spec in specs) {
+            try {
+                fetchEbicsC5x(spec.orderType, client, bankConnectionId, 
spec.orderParams, subscriberDetails)
+            } catch (e: Exception) {
+                logger.warn("Ingestion failed for $spec")
+            }
+        }
+    }
+
+    override suspend fun submitPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
+        val r = transaction {
+            val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
+                ?: throw NexusError(HttpStatusCode.NotFound, "payment 
initiation not found")
+            val conn = paymentInitiation.bankAccount.defaultBankConnection
+                ?: throw NexusError(HttpStatusCode.NotFound, "no default bank 
connection available for submission")
+            val subscriberDetails = 
getEbicsSubscriberDetails(conn.connectionId)
+            val painMessage = createPain001document(
+                NexusPaymentInitiationData(
+                    debtorIban = paymentInitiation.bankAccount.iban,
+                    debtorBic = paymentInitiation.bankAccount.bankCode,
+                    debtorName = paymentInitiation.bankAccount.accountHolder,
+                    currency = paymentInitiation.currency,
+                    amount = paymentInitiation.sum.toString(),
+                    creditorIban = paymentInitiation.creditorIban,
+                    creditorName = paymentInitiation.creditorName,
+                    creditorBic = paymentInitiation.creditorBic,
+                    paymentInformationId = 
paymentInitiation.paymentInformationId,
+                    preparationTimestamp = paymentInitiation.preparationDate,
+                    subject = paymentInitiation.subject,
+                    instructionId = paymentInitiation.instructionId,
+                    endToEndId = paymentInitiation.endToEndId,
+                    messageId = paymentInitiation.messageId
+                )
+            )
+            if (!XMLUtil.validateFromString(painMessage)) throw NexusError(
+                HttpStatusCode.InternalServerError, "Pain.001 message is 
invalid."
+            )
+            object {
+                val subscriberDetails = subscriberDetails
+                val painMessage = painMessage
+            }
+        }
+        doEbicsUploadTransaction(
+            httpClient,
+            r.subscriberDetails,
+            "CCT",
+            r.painMessage.toByteArray(Charsets.UTF_8),
+            EbicsStandardOrderParams()
+        )
+        transaction {
+            val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
+                ?: throw NexusError(HttpStatusCode.NotFound, "payment 
initiation not found")
+            paymentInitiation.submitted = true
+            paymentInitiation.submissionDate = LocalDateTime.now().millis()
+        }
+    }
+
+    override fun exportAnalogDetails(conn: NexusBankConnectionEntity): 
ByteArray {
+        val ebicsSubscriber = transaction { 
getEbicsSubscriberDetails(conn.connectionId) }
+        val po = ByteArrayOutputStream()
+        val pdfWriter = PdfWriter(po)
+        val pdfDoc = PdfDocument(pdfWriter)
+        val date = LocalDateTime.now()
+        val dateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
+
+        fun writeCommon(doc: Document) {
+            doc.add(
+                Paragraph(
+                    """
             Datum: $dateStr
             Teilnehmer: ${conn.id.value}
             Host-ID: ${ebicsSubscriber.hostId}
@@ -660,130 +554,235 @@ fun getEbicsKeyLetterPdf(conn: 
NexusBankConnectionEntity): ByteArray {
             Partner-ID: ${ebicsSubscriber.partnerId}
             ES version: A006
         """.trimIndent()
+                )
             )
-        )
-    }
-
-    fun writeKey(doc: Document, priv: RSAPrivateCrtKey) {
-        val pub = CryptoUtil.getRsaPublicFromPrivate(priv)
-        val hash = CryptoUtil.getEbicsPublicKeyHash(pub)
-        
doc.add(Paragraph("Exponent:\n${formatHex(pub.publicExponent.toByteArray())}"))
-        doc.add(Paragraph("Modulus:\n${formatHex(pub.modulus.toByteArray())}"))
-        doc.add(Paragraph("SHA-256 hash:\n${formatHex(hash)}"))
-    }
-
-    fun writeSigLine(doc: Document) {
-        doc.add(Paragraph("Ort / Datum: ________________"))
-        doc.add(Paragraph("Firma / Name: ________________"))
-        doc.add(Paragraph("Unterschrift: ________________"))
-    }
-
-    Document(pdfDoc).use {
-        it.add(Paragraph("Signaturschlüssel").setFontSize(24f))
-        writeCommon(it)
-        it.add(Paragraph("Öffentlicher Schlüssel (Public key for the 
electronic signature)"))
-        writeKey(it, ebicsSubscriber.customerSignPriv)
-        it.add(Paragraph("\n"))
-        writeSigLine(it)
-        it.add(AreaBreak())
-
-        it.add(Paragraph("Authentifikationsschlüssel").setFontSize(24f))
-        writeCommon(it)
-        it.add(Paragraph("Öffentlicher Schlüssel (Public key for the 
identification and authentication signature)"))
-        writeKey(it, ebicsSubscriber.customerAuthPriv)
-        it.add(Paragraph("\n"))
-        writeSigLine(it)
-        it.add(AreaBreak())
-
-        it.add(Paragraph("Verschlüsselungsschlüssel").setFontSize(24f))
-        writeCommon(it)
-        it.add(Paragraph("Öffentlicher Schlüssel (Public encryption key)"))
-        writeKey(it, ebicsSubscriber.customerSignPriv)
-        it.add(Paragraph("\n"))
-        writeSigLine(it)
-    }
-    pdfWriter.flush()
-    return po.toByteArray()
-}
+        }
 
-suspend fun submitEbicsPaymentInitiation(httpClient: HttpClient, 
paymentInitiationId: Long) {
-    val r = transaction {
-        val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
-            ?: throw NexusError(HttpStatusCode.NotFound, "payment initiation 
not found")
-        val conn = paymentInitiation.bankAccount.defaultBankConnection
-            ?: throw NexusError(HttpStatusCode.NotFound, "no default bank 
connection available for submission")
-        val subscriberDetails = getEbicsSubscriberDetails(conn.connectionId)
-        val painMessage = createPain001document(
-            NexusPaymentInitiationData(
-                debtorIban = paymentInitiation.bankAccount.iban,
-                debtorBic = paymentInitiation.bankAccount.bankCode,
-                debtorName = paymentInitiation.bankAccount.accountHolder,
-                currency = paymentInitiation.currency,
-                amount = paymentInitiation.sum.toString(),
-                creditorIban = paymentInitiation.creditorIban,
-                creditorName = paymentInitiation.creditorName,
-                creditorBic = paymentInitiation.creditorBic,
-                paymentInformationId = paymentInitiation.paymentInformationId,
-                preparationTimestamp = paymentInitiation.preparationDate,
-                subject = paymentInitiation.subject,
-                instructionId = paymentInitiation.instructionId,
-                endToEndId = paymentInitiation.endToEndId,
-                messageId = paymentInitiation.messageId
+        fun writeKey(doc: Document, priv: RSAPrivateCrtKey) {
+            val pub = CryptoUtil.getRsaPublicFromPrivate(priv)
+            val hash = CryptoUtil.getEbicsPublicKeyHash(pub)
+            
doc.add(Paragraph("Exponent:\n${formatHex(pub.publicExponent.toByteArray())}"))
+            
doc.add(Paragraph("Modulus:\n${formatHex(pub.modulus.toByteArray())}"))
+            doc.add(Paragraph("SHA-256 hash:\n${formatHex(hash)}"))
+        }
+
+        fun writeSigLine(doc: Document) {
+            doc.add(Paragraph("Ort / Datum: ________________"))
+            doc.add(Paragraph("Firma / Name: ________________"))
+            doc.add(Paragraph("Unterschrift: ________________"))
+        }
+
+        Document(pdfDoc).use {
+            it.add(Paragraph("Signaturschlüssel").setFontSize(24f))
+            writeCommon(it)
+            it.add(Paragraph("Öffentlicher Schlüssel (Public key for the 
electronic signature)"))
+            writeKey(it, ebicsSubscriber.customerSignPriv)
+            it.add(Paragraph("\n"))
+            writeSigLine(it)
+            it.add(AreaBreak())
+
+            it.add(Paragraph("Authentifikationsschlüssel").setFontSize(24f))
+            writeCommon(it)
+            it.add(Paragraph("Öffentlicher Schlüssel (Public key for the 
identification and authentication signature)"))
+            writeKey(it, ebicsSubscriber.customerAuthPriv)
+            it.add(Paragraph("\n"))
+            writeSigLine(it)
+            it.add(AreaBreak())
+
+            it.add(Paragraph("Verschlüsselungsschlüssel").setFontSize(24f))
+            writeCommon(it)
+            it.add(Paragraph("Öffentlicher Schlüssel (Public encryption key)"))
+            writeKey(it, ebicsSubscriber.customerSignPriv)
+            it.add(Paragraph("\n"))
+            writeSigLine(it)
+        }
+        pdfWriter.flush()
+        return po.toByteArray()
+    }
+    override fun exportBackup(bankConnectionId: String, passphrase: String): 
JsonNode {
+        val subscriber = transaction { 
getEbicsSubscriberDetails(bankConnectionId) }
+        val ret = EbicsKeysBackupJson(
+            type = "ebics",
+            userID = subscriber.userId,
+            hostID = subscriber.hostId,
+            partnerID = subscriber.partnerId,
+            ebicsURL = subscriber.ebicsUrl,
+            authBlob = bytesToBase64(
+                CryptoUtil.encryptKey(
+                    subscriber.customerAuthPriv.encoded,
+                    passphrase
+                )
+            ),
+            encBlob = bytesToBase64(
+                CryptoUtil.encryptKey(
+                    subscriber.customerEncPriv.encoded,
+                    passphrase
+                )
+            ),
+            sigBlob = bytesToBase64(
+                CryptoUtil.encryptKey(
+                    subscriber.customerSignPriv.encoded,
+                    passphrase
+                )
             )
         )
-        if (!XMLUtil.validateFromString(painMessage)) throw NexusError(
-            HttpStatusCode.InternalServerError, "Pain.001 message is invalid."
+        val mapper = ObjectMapper()
+        return mapper.valueToTree(ret)
+    }
+
+    override fun getConnectionDetails(conn: NexusBankConnectionEntity): 
JsonNode {
+        val ebicsSubscriber = transaction { 
getEbicsSubscriberDetails(conn.connectionId) }
+        val mapper = ObjectMapper()
+        val details = mapper.createObjectNode()
+        details.put("ebicsUrl", ebicsSubscriber.ebicsUrl)
+        details.put("ebicsHostId", ebicsSubscriber.hostId)
+        details.put("partnerId", ebicsSubscriber.partnerId)
+        details.put("userId", ebicsSubscriber.userId)
+        val node = mapper.createObjectNode()
+        node.put("type", conn.type)
+        node.put("owner", conn.owner.username)
+        node.put("ready", true) // test with #6715 needed.
+        node.set<JsonNode>("details", details)
+        return node
+    }
+
+    override fun createConnection(connId: String, user: NexusUserEntity, data: 
JsonNode) {
+        val bankConn = NexusBankConnectionEntity.new {
+            this.connectionId = connId
+            owner = user
+            type = "ebics"
+        }
+        val newTransportData = jacksonObjectMapper(
+        ).treeToValue(data, EbicsNewTransport::class.java) ?: throw NexusError(
+            HttpStatusCode.BadRequest, "Ebics details not found in request"
         )
-        object {
-            val subscriberDetails = subscriberDetails
-            val painMessage = painMessage
+        val pairA = CryptoUtil.generateRsaKeyPair(2048)
+        val pairB = CryptoUtil.generateRsaKeyPair(2048)
+        val pairC = CryptoUtil.generateRsaKeyPair(2048)
+        EbicsSubscriberEntity.new {
+            ebicsURL = newTransportData.ebicsURL
+            hostID = newTransportData.hostID
+            partnerID = newTransportData.partnerID
+            userID = newTransportData.userID
+            systemID = newTransportData.systemID
+            signaturePrivateKey = ExposedBlob((pairA.private.encoded))
+            encryptionPrivateKey = ExposedBlob((pairB.private.encoded))
+            authenticationPrivateKey = ExposedBlob((pairC.private.encoded))
+            nexusBankConnection = bankConn
+            ebicsIniState = EbicsInitState.NOT_SENT
+            ebicsHiaState = EbicsInitState.NOT_SENT
+        }
+    }
+
+    override fun createConnectionFromBackup(
+        connId: String,
+        user: NexusUserEntity,
+        passphrase: String?,
+        backup: JsonNode
+    ) {
+        if (passphrase === null) {
+            throw NexusError(HttpStatusCode.BadRequest, "EBICS backup needs 
passphrase")
+        }
+        val bankConn = NexusBankConnectionEntity.new {
+            connectionId = connId
+            owner = user
+            type = "ebics"
+        }
+        val ebicsBackup = jacksonObjectMapper().treeToValue(backup, 
EbicsKeysBackupJson::class.java)
+        val (authKey, encKey, sigKey) = try {
+            Triple(
+                CryptoUtil.decryptKey(
+                    
EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.authBlob)),
+                    passphrase
+                ),
+                CryptoUtil.decryptKey(
+                    
EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.encBlob)),
+                    passphrase
+                ),
+                CryptoUtil.decryptKey(
+                    
EncryptedPrivateKeyInfo(base64ToBytes(ebicsBackup.sigBlob)),
+                    passphrase
+                )
+            )
+        } catch (e: Exception) {
+            e.printStackTrace()
+            logger.info("Restoring keys failed, probably due to wrong 
passphrase")
+            throw NexusError(
+                HttpStatusCode.BadRequest,
+                "Bad backup given"
+            )
         }
+        try {
+            EbicsSubscriberEntity.new {
+                ebicsURL = ebicsBackup.ebicsURL
+                hostID = ebicsBackup.hostID
+                partnerID = ebicsBackup.partnerID
+                userID = ebicsBackup.userID
+                signaturePrivateKey = ExposedBlob(sigKey.encoded)
+                encryptionPrivateKey = ExposedBlob((encKey.encoded))
+                authenticationPrivateKey = ExposedBlob((authKey.encoded))
+                nexusBankConnection = bankConn
+                ebicsIniState = EbicsInitState.UNKNOWN
+                ebicsHiaState = EbicsInitState.UNKNOWN
+            }
+        } catch (e: Exception) {
+            throw NexusError(
+                HttpStatusCode.BadRequest,
+                "exception: $e"
+            )
+        }
+        return
     }
-    doEbicsUploadTransaction(
-        httpClient,
-        r.subscriberDetails,
-        "CCT",
-        r.painMessage.toByteArray(Charsets.UTF_8),
-        EbicsStandardOrderParams()
-    )
-    transaction {
-        val paymentInitiation = 
PaymentInitiationEntity.findById(paymentInitiationId)
-            ?: throw NexusError(HttpStatusCode.NotFound, "payment initiation 
not found")
-        paymentInitiation.submitted = true
-        paymentInitiation.submissionDate = LocalDateTime.now().millis()
-    }
-}
 
+    override suspend fun fetchAccounts(client: HttpClient, connId: String) {
+        val subscriberDetails = transaction { 
getEbicsSubscriberDetails(connId) }
+        val response = doEbicsDownloadTransaction(
+            client, subscriberDetails, "HTD", EbicsStandardOrderParams()
+        )
+        when (response) {
+            is EbicsDownloadBankErrorResult -> {
+                throw NexusError(
+                    HttpStatusCode.BadGateway,
+                    response.returnCode.errorCode
+                )
+            }
+            is EbicsDownloadSuccessResult -> {
+                val payload = 
XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
+                    response.orderData.toString(Charsets.UTF_8)
+                )
+                transaction {
+                    payload.value.partnerInfo.accountInfoList?.forEach { 
accountInfo ->
+                        val conn = 
NexusBankConnectionEntity.findByName(connId) ?: throw NexusError(
+                            HttpStatusCode.NotFound,
+                            "bank connection not found"
+                        )
 
-fun createEbicsBankConnection(bankConnectionName: String, user: 
NexusUserEntity, data: JsonNode) {
-    val bankConn = NexusBankConnectionEntity.new {
-        this.connectionId = bankConnectionName
-        owner = user
-        type = "ebics"
-    }
-    val newTransportData = jacksonObjectMapper(
-    ).treeToValue(data, EbicsNewTransport::class.java) ?: throw NexusError(
-        HttpStatusCode.BadRequest, "Ebics details not found in request"
-    )
-    val pairA = CryptoUtil.generateRsaKeyPair(2048)
-    val pairB = CryptoUtil.generateRsaKeyPair(2048)
-    val pairC = CryptoUtil.generateRsaKeyPair(2048)
-    EbicsSubscriberEntity.new {
-        ebicsURL = newTransportData.ebicsURL
-        hostID = newTransportData.hostID
-        partnerID = newTransportData.partnerID
-        userID = newTransportData.userID
-        systemID = newTransportData.systemID
-        signaturePrivateKey = ExposedBlob((pairA.private.encoded))
-        encryptionPrivateKey = ExposedBlob((pairB.private.encoded))
-        authenticationPrivateKey = ExposedBlob((pairC.private.encoded))
-        nexusBankConnection = bankConn
-        ebicsIniState = EbicsInitState.NOT_SENT
-        ebicsHiaState = EbicsInitState.NOT_SENT
+                        val isDuplicate = OfferedBankAccountsTable.select {
+                            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[bankCode] = 
accountInfo.bankCodeList?.filterIsInstance<EbicsTypes.GeneralBankCode>()
+                                ?.find { it.international }?.value
+                                ?: throw NexusError(
+                                    HttpStatusCode.NotFound,
+                                    reason = "bank gave no BIC"
+                                )
+                            newRow[bankConnection] = 
requireBankConnectionInternal(connId).id
+                            newRow[offeredAccountId] = accountInfo.id
+                        }
+                    }
+                }
+            }
+        }
     }
-}
 
-class EbicsBankConnectionProtocol: BankConnectionProtocol {
     override suspend fun connect(client: HttpClient, connId: String) {
         val subscriber = transaction { getEbicsSubscriberDetails(connId) }
         if (subscriber.bankAuthPub != null && subscriber.bankEncPub != null) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index e8e9895..37ef78b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -736,31 +736,12 @@ fun serverMain(dbName: String, host: String, port: Int) {
                             if (type == null || !type.isTextual) {
                                 throw NexusError(HttpStatusCode.BadRequest, 
"backup needs type")
                             }
-                            when (type.textValue()) {
-                                "ebics" -> {
-                                    
createEbicsBankConnectionFromBackup(body.name, user, body.passphrase, body.data)
-                                }
-                                else -> {
-                                    throw 
NexusError(HttpStatusCode.BadRequest, "backup type not supported")
-                                }
-                            }
+                            val plugin = getConnectionPlugin(type.textValue())
+                            plugin.createConnectionFromBackup(body.name, user, 
body.passphrase, body.data)
                         }
                         is CreateBankConnectionFromNewRequestJson -> {
-                            when (body.type) {
-                                "ebics" -> {
-                                    createEbicsBankConnection(body.name, user, 
body.data)
-                                }
-                                "loopback" -> {
-                                    createLoopbackBankConnection(body.name, 
user, body.data)
-
-                                }
-                                else -> {
-                                    throw NexusError(
-                                        HttpStatusCode.BadRequest,
-                                        "connection type ${body.type} not 
supported"
-                                    )
-                                }
-                            }
+                            val plugin = getConnectionPlugin(body.type)
+                            plugin.createConnection(body.name, user, body.data)
                         }
                     }
                 }
@@ -802,17 +783,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
                 requireSuperuser(call.request)
                 val resp = transaction {
                     val conn = requireBankConnection(call, "connectionName")
-                    when (conn.type) {
-                        "ebics" -> {
-                            getEbicsConnectionDetails(conn)
-                        }
-                        else -> {
-                            throw NexusError(
-                                HttpStatusCode.BadRequest,
-                                "bank connection is not of type 'ebics' (but 
'${conn.type}')"
-                            )
-                        }
-                    }
+                    getConnectionPlugin(conn.type).getConnectionDetails(conn)
                 }
                 call.respond(resp)
             }
@@ -823,17 +794,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
                 val body = call.receive<BackupRequestJson>()
                 val response = run {
                     val conn = requireBankConnection(call, "connectionName")
-                    when (conn.type) {
-                        "ebics" -> {
-                            exportEbicsKeyBackup(conn.connectionId, 
body.passphrase)
-                        }
-                        else -> {
-                            throw NexusError(
-                                HttpStatusCode.BadRequest,
-                                "bank connection is not of type 'ebics' (but 
'${conn.type}')"
-                            )
-                        }
-                    }
+                    
getConnectionPlugin(conn.type).exportBackup(conn.connectionId, body.passphrase)
                 }
                 call.response.headers.append("Content-Disposition", 
"attachment")
                 call.respond(
@@ -859,13 +820,8 @@ fun serverMain(dbName: String, host: String, port: Int) {
                     authenticateRequest(call.request)
                     requireBankConnection(call, "connectionName")
                 }
-                when (conn.type) {
-                    "ebics" -> {
-                        val pdfBytes = getEbicsKeyLetterPdf(conn)
-                        call.respondBytes(pdfBytes, ContentType("application", 
"pdf"))
-                    }
-                    else -> throw NexusError(HttpStatusCode.NotImplemented, 
"keyletter not supported for ${conn.type}")
-                }
+                val pdfBytes = 
getConnectionPlugin(conn.type).exportAnalogDetails(conn)
+                call.respondBytes(pdfBytes, ContentType("application", "pdf"))
             }
 
             get("/bank-connections/{connectionName}/messages") {
@@ -994,15 +950,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
                         authenticateRequest(call.request)
                         requireBankConnection(call, "connid")
                     }
-                    when (conn.type) {
-                        "ebics" -> {
-                            ebicsFetchAccounts(conn.connectionId, client)
-                        }
-                        else -> throw NexusError(
-                            HttpStatusCode.NotImplemented,
-                            "connection not supported for ${conn.type}"
-                        )
-                    }
+                    getConnectionPlugin(conn.type).fetchAccounts(client, 
conn.connectionId)
                     call.respond(object {})
                 }
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]