gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 02/02: More separation for transport types.


From: gnunet
Subject: [libeufin] 02/02: More separation for transport types.
Date: Tue, 28 Apr 2020 20:32:40 +0200

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

marcello pushed a commit to branch master
in repository libeufin.

commit e5e2ba20a612aaebeb3365f6d07640abb22f8d45
Author: Marcello Stanisci <address@hidden>
AuthorDate: Tue Apr 28 18:12:54 2020 +0200

    More separation for transport types.
---
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |    6 +-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt |   29 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |  104 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 1172 +++++++-------------
 nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt |    7 +-
 nexus/src/test/kotlin/authentication.kt            |   18 +-
 6 files changed, 526 insertions(+), 810 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index c10037c..24fddf3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -210,13 +210,15 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : 
Entity<Int>(id) {
 
 object NexusUsersTable : IdTable<String>() {
     override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
-    val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
+    val ebicsSubscriber = reference("ebicsSubscriber", 
EbicsSubscribersTable).nullable()
+    val testSubscriber = reference("testSubscriber", 
EbicsSubscribersTable).nullable()
     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 ebicsSubscriber by EbicsSubscriberEntity optionalReferencedOn 
NexusUsersTable.ebicsSubscriber
+    var testSubscriber by EbicsSubscriberEntity optionalReferencedOn 
NexusUsersTable.testSubscriber
     var password by NexusUsersTable.password
 }
 
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index bdde897..a9353a0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -20,10 +20,10 @@ import java.time.ZonedDateTime
 import java.time.Instant
 import java.time.ZoneId
 
-fun getSubscriberEntityFromNexusUserId(nexusUserId: String): 
EbicsSubscriberEntity {
+fun getSubscriberEntityFromNexusUserId(nexusUserId: String?): 
EbicsSubscriberEntity {
     return transaction {
-        val nexusUser = expectNexusIdTransaction(nexusUserId)
-        nexusUser.ebicsSubscriber
+        val nexusUser = expectNexusIdTransaction(expectId(nexusUserId))
+        getEbicsSubscriberFromUser(nexusUser)
     }
 }
 
@@ -128,10 +128,21 @@ fun getSubscriberDetailsInternal(subscriber: 
EbicsSubscriberEntity): EbicsClient
     )
 }
 
+/** Return non null Ebics subscriber, or throw error otherwise. */
+fun getEbicsSubscriberFromUser(nexusUser: NexusUserEntity): 
EbicsSubscriberEntity {
+    return nexusUser.ebicsSubscriber ?: throw NexusError(
+        HttpStatusCode.NotFound,
+        "Ebics subscriber was never activated"
+    )
+}
+
 fun getSubscriberDetailsFromNexusUserId(id: String): 
EbicsClientSubscriberDetails {
     return transaction {
         val nexusUser = expectNexusIdTransaction(id)
-        getSubscriberDetailsInternal(nexusUser.ebicsSubscriber)
+        getSubscriberDetailsInternal(nexusUser.ebicsSubscriber ?: throw 
NexusError(
+            HttpStatusCode.NotFound,
+            "Cannot get details for non-activated subscriber!"
+        ))
     }
 }
 
@@ -404,6 +415,16 @@ fun subscriberHasRights(subscriber: EbicsSubscriberEntity, 
bankAccount: BankAcco
     return row != null
 }
 
+/** Check if the nexus user is allowed to use the claimed bank account.  */
+fun userHasRights(subscriber: NexusUserEntity, bankAccount: 
BankAccountEntity): Boolean {
+    val row = transaction {
+        UserToBankAccountEntity.find {
+            UserToBankAccountsTable.bankAccount eq bankAccount.id and
+                    (UserToBankAccountsTable.nexusUser 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
index 9108b41..201b7a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -4,6 +4,7 @@ import tech.libeufin.util.Amount
 import tech.libeufin.util.EbicsDateRange
 import tech.libeufin.util.EbicsOrderParams
 import tech.libeufin.util.EbicsStandardOrderParams
+import java.lang.NullPointerException
 import java.time.LocalDate
 
 data class EbicsBackupRequestJson(
@@ -54,54 +55,12 @@ data class EbicsKeysBackupJson(
     val passphrase: String? = null
 )
 
-
 data class EbicsPubKeyInfo(
     val authPub: String,
     val encPub: String,
     val sigPub: String
 )
 
-/**
- * This object is POSTed by clients _after_ having created
- * a EBICS subscriber at the sandbox.
- */
-data class EbicsSubscriberInfoRequestJson(
-    val ebicsURL: String,
-    val hostID: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String? = null,
-    val password: String? = null
-)
-
-/**
- * Contain the ID that identifies the new user in the Nexus system.
- */
-data class EbicsSubscriberInfoResponseJson(
-    val nexusUserID: String,
-    val ebicsURL: String,
-    val hostID: String,
-    val partnerID: String,
-    val userID: String,
-    val systemID: String? = null
-)
-
-data class Pain001Data(
-    val creditorIban: String,
-    val creditorBic: String,
-    val creditorName: String,
-    val sum: Amount,
-    val currency: String = "EUR",
-    val subject: String
-)
-
-/**
- * Admin call that tells all the subscribers managed by Nexus.
- */
-data class EbicsSubscribersResponseJson(
-    val ebicsSubscribers: MutableList<EbicsSubscriberInfoResponseJson> = 
mutableListOf()
-)
-
 data class ProtocolAndVersionJson(
     val protocol: String,
     val version: String
@@ -131,6 +90,56 @@ data class BankAccountsInfoResponse(
     var accounts: MutableList<BankAccountInfoElement> = mutableListOf()
 )
 
+/** THE NEXUS USER */
+
+/** SHOWS details about one user */
+data class NexusUser(
+    val userID: String,
+    val transports: MutableList<Any> = mutableListOf()
+)
+
+/** Instructs the nexus to CREATE a new user */
+data class NexusUserRequest(
+    val userID: String,
+    val password: String?
+)
+
+/** Collection of all the nexus users existing in the system */
+data class NexusUsers(
+    val users: MutableList<NexusUser> = mutableListOf()
+)
+
+/************************************/
+
+/** TRANSPORT TYPES */
+
+/** Instructs the nexus to CREATE a new Ebics subscriber.
+ * Note that the nexus user to which the subscriber must be
+ * associated is extracted from other HTTP details.
+ *
+ * This same structure can be user to SHOW one Ebics subscriber
+ * existing at the nexus.
+ */
+data class EbicsSubscriber(
+    val ebicsURL: String,
+    val hostID: String,
+    val partnerID: String,
+    val userID: String,
+    val systemID: String? = null
+)
+
+/** Type representing the "test" transport.  Test transport
+ * does not cooperate with the bank/sandbox in order to obtain
+ * data about one user.  All the data is just mocked internally
+ * at the NEXUS.
+ */
+class TestSubscriber()
+
+
+/** PAYMENT INSTRUCTIONS TYPES */
+
+/** Represents a prepared payment at the nexus.  This structure is
+ * used to SHOW a prepared payment to the called.  */
 data class PaymentInfoElement(
     val debtorAccount: String,
     val creditorIban: String,
@@ -140,7 +149,16 @@ data class PaymentInfoElement(
     val sum: Amount,
     val submitted: Boolean
 )
-
 data class PaymentsInfo(
     var payments: MutableList<PaymentInfoElement> = mutableListOf()
+)
+
+/** This structure is used to INSTRUCT the nexus to prepare such payment.  */
+data class Pain001Data(
+    val creditorIban: String,
+    val creditorBic: String,
+    val creditorName: String,
+    val sum: Amount,
+    val currency: String = "EUR",
+    val subject: String
 )
\ 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
index 201367c..8f3f7a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -133,6 +133,9 @@ fun main() {
         }
 
         routing {
+
+            /** General / debug endpoints */
+
             get("/") {
                 call.respondText("Hello by Nexus!\n")
                 return@get
@@ -144,54 +147,71 @@ fun main() {
                 return@get
             }
 
-            post("/ebics/subscribers/{id}/sendPTK") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                println("PTK order params: $orderParams")
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "PTK", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
+            /** USER endpoints (no EBICS) */
+
+            /** Lists the users known to this system */
+            get("/users") {
+                val ret = NexusUsers()
+                transaction {
+                    NexusUserEntity.all().forEach {
+                        val nexusUser = NexusUser(userID = it.id.value)
+                        val ebicsSubscriber = it.ebicsSubscriber
+                        if (ebicsSubscriber != null) {
+                            nexusUser.transports.add(
+                                EbicsSubscriber(
+                                    userID = ebicsSubscriber.userID,
+                                    ebicsURL = ebicsSubscriber.ebicsURL,
+                                    hostID = ebicsSubscriber.hostID,
+                                    partnerID = ebicsSubscriber.partnerID,
+                                    systemID = ebicsSubscriber.systemID
+                                )
+                            )
+                        }
+                        if (it.testSubscriber != null) {
+                            nexusUser.transports.add(TestSubscriber())
+                        }
                     }
                 }
-                return@post
+                call.respond(ret)
+                return@get
             }
 
-            post("/ebics/subscribers/{id}/sendHAC") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAC", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
+            /** Get all the details associated with a NEXUS user */
+            get("/user/{id}") {
+                val response = transaction {
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    NexusUser(
+                        userID = nexusUser.id.value
+                    )
+                }
+                call.respond(HttpStatusCode.OK, response)
+                return@get
+            }
+
+            /** Make a new NEXUS user in the system */
+            post("/users/{id}") {
+                val newUserId = expectId(call.parameters["id"])
+                val body = call.receive<NexusUserRequest>()
+                transaction {
+                    NexusUserEntity.new(id = newUserId) {
+                        password = if (body.password != null) {
+                            
SerialBlob(CryptoUtil.hashStringSHA256(body.password))
+                        } else {
+                            logger.debug("No password set for $newUserId")
+                            null
+                        }
                     }
                 }
+                call.respondText(
+                    "New NEXUS user registered. ID: $newUserId",
+                    ContentType.Text.Plain,
+                    HttpStatusCode.OK
+                )
+                return@post
             }
-            get("/ebics/subscribers/{id}/accounts") {
+
+            /** List all the bank accounts associated with a given NEXUS user. 
 */
+            get("/users/{id}/accounts") {
                 // this information is only avaiable *after* HTD or HKD has 
been called
                 val id = expectId(call.parameters["id"])
                 val ret = BankAccountsInfoResponse()
@@ -215,40 +235,14 @@ fun main() {
                 )
                 return@get
             }
-            /**
-             * This endpoint gathers all the data needed to create a payment 
and persists it
-             * into the database.  However, it does NOT perform the payment 
itself!
-             */
-            post("/ebics/subscribers/{id}/accounts/{acctid}/prepare-payment") {
-                val acctid = transaction {
-                    val accountInfo = 
expectAcctidTransaction(call.parameters["acctid"])
-                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
-                    if (!subscriberHasRights(nexusUser.ebicsSubscriber, 
accountInfo)) {
-                        throw NexusError(
-                            HttpStatusCode.BadRequest,
-                            "Claimed bank account '${accountInfo.id}' doesn't 
belong to user '${nexusUser.id.value}'!"
-                        )
-                    }
-                    accountInfo.id.value
-                }
-                val pain001data = call.receive<Pain001Data>()
-                createPain001entity(pain001data, acctid)
-                call.respondText(
-                    "Payment instructions persisted in DB",
-                    ContentType.Text.Plain, HttpStatusCode.OK
-                )
-                return@post
-            }
-            /**
-             * list all the prepared payments related to customer {id}
-             */
-            get("/ebics/subscribers/{id}/payments") {
+            /** Show list of payments prepared by calling user.  */
+            get("/users/{id}/payments") {
                 val nexusUserId = expectId(call.parameters["id"])
                 val ret = PaymentsInfo()
                 transaction {
                     val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    val bankAccountsMap = EbicsToBankAccountEntity.find {
-                        EbicsToBankAccountsTable.ebicsSubscriber eq 
nexusUser.ebicsSubscriber.id
+                    val bankAccountsMap = UserToBankAccountEntity.find {
+                        UserToBankAccountsTable.nexusUser eq nexusUser.id
                     }
                     bankAccountsMap.forEach {
                         Pain001Entity.find {
@@ -271,441 +265,131 @@ fun main() {
                 call.respond(ret)
                 return@get
             }
-            /**
-             * This function triggers the Nexus to perform all those 
un-submitted payments.
-             * Ideally, this logic will be moved into some more automatic 
mechanism.
-             * NOTE: payments are not yet marked as "done" after this function 
returns.  This
-             * should be done AFTER the PAIN.002 data corresponding to a 
payment witnesses it.
-             */
-            post("/ebics/admin/execute-payments") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
-                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
-                }
-                logger.debug("Uploading PAIN.001: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberDetails,
-                    "CCT",
-                    painDoc.toByteArray(Charsets.UTF_8),
-                    EbicsStandardOrderParams()
-                )
-                /* flow here == no errors occurred */
-                transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
-                    )
-                    payment.submitted = true
+            post("/users/{id}/accounts/{acctid}/prepare-payment") {
+                val acctid = transaction {
+                    val accountInfo = 
expectAcctidTransaction(call.parameters["acctid"])
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    if (!userHasRights(nexusUser, accountInfo)) {
+                        throw NexusError(
+                            HttpStatusCode.BadRequest,
+                            "Claimed bank account '${accountInfo.id}' doesn't 
belong to user '${nexusUser.id.value}'!"
+                        )
+                    }
+                    accountInfo.id.value
                 }
+                val pain001data = call.receive<Pain001Data>()
+                createPain001entity(pain001data, acctid)
                 call.respondText(
-                    "CCT message submitted to the bank",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
+                    "Payment instructions persisted in DB",
+                    ContentType.Text.Plain, HttpStatusCode.OK
                 )
                 return@post
             }
-            /**
-             * This function triggers the Nexus to perform all those 
un-submitted payments.
-             * Ideally, this logic will be moved into some more automatic 
mechanism.
-             * NOTE: payments are not yet marked as "done" after this function 
returns.  This
-             * should be done AFTER the PAIN.002 data corresponding to a 
payment witnesses it.
-             */
-            post("/ebics/admin/execute-payments-ccc") {
-                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
-                    val entity = Pain001Entity.find {
-                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
-                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
-                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
-                }
-                logger.debug("Uploading PAIN.001 via CCC: ${painDoc}")
-                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberDetails,
-                    "CCC",
-                    painDoc.toByteArray(Charsets.UTF_8).zip(),
-                    EbicsStandardOrderParams()
-                )
-                /* flow here == no errors occurred */
+
+            /** Associate a EBICS subscriber to the existing user */
+            post("/ebics/{id}/subscriber") {
+                val body = call.receive<EbicsSubscriber>()
+                val pairA = CryptoUtil.generateRsaKeyPair(2048)
+                val pairB = CryptoUtil.generateRsaKeyPair(2048)
+                val pairC = CryptoUtil.generateRsaKeyPair(2048)
+
                 transaction {
-                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
-                        HttpStatusCode.InternalServerError,
-                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
+                    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)
+                    }
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    nexusUser.ebicsSubscriber = newEbicsSubscriber
+                }
+
+            }
+            post("/ebics/subscribers/{id}/restoreBackup") {
+                val body = call.receive<EbicsKeysBackupJson>()
+                val nexusId = expectId(call.parameters["id"])
+                val subscriber = transaction {
+                    NexusUserEntity.findById(nexusId)
+                }
+                if (subscriber != null) {
+                    call.respond(
+                        HttpStatusCode.Conflict,
+                        NexusErrorJson("ID exists, please choose a new one")
                     )
-                    payment.submitted = true
+                    return@post
+                }
+                val (authKey, encKey, sigKey) = try {
+                    Triple(
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.authBlob)), body.passphrase!!
+                        ),
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.encBlob)), body.passphrase
+                        ),
+                        CryptoUtil.decryptKey(
+                            
EncryptedPrivateKeyInfo(base64ToBytes(body.sigBlob)), body.passphrase
+                        )
+                    )
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                    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: $nexusId")
+                try {
+                    transaction {
+                        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 into database"))
+                    return@post
                 }
                 call.respondText(
-                    "CCC message submitted to the bank",
+                    "Keys successfully restored",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
 
-            post("/ebics/subscribers/{id}/fetch-payment-status") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "CRZ",
-                    orderParams
-                )
-                when (response) {
-                    is EbicsDownloadSuccessResult ->
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    /**
-                     * NOTE: flow gets here when the bank-technical return 
code is
-                     * different from 000000.  This happens also for 090005 
(no data available)
-                     */
-                    else -> call.respond(NexusErrorJson("Could not download 
any PAIN.002"))
-                }
-                return@post
-            }
-            post("/ebics/subscribers/{id}/collect-transactions-c52") {
-                // FIXME(florian): Download C52 and store the result in the 
right database table
+            /** EBICS CONVENIENCE */
 
-            }
-            get("/ebics/subscribers/{id}/show-collected-transactions-c53") {
-                val id = expectId(call.parameters["id"])
-                var ret = ""
-                transaction {
-                    val subscriber: EbicsSubscriberEntity = 
getSubscriberEntityFromNexusUserId(id)
-                    RawBankTransactionEntity.find {
-                        RawBankTransactionsTable.nexusSubscriber eq 
subscriber.id.value
-                    }.forEach {
-                        ret += "###\nDebitor: ${it.debitorIban}\nCreditor: 
${it.creditorIban}\nAmount: ${it.currency}:${it.amount}\nDate: 
${it.bookingDate}\n"
-                    }
-                }
-                call.respondText(
-                    ret,
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-
-                return@get
-            }
-
-            /* Taler class will initialize all the relevant handlers.  */
-            Taler(this)
-
-            post("/ebics/subscribers/{id}/collect-transactions-c53") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                when (val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)) {
-                    is EbicsDownloadSuccessResult -> {
-                        /**
-                         * The current code is _heavily_ dependent on the way 
GLS returns
-                         * data.  For example, GLS makes one ZIP entry for 
each "Ntry" element
-                         * (a bank transfer), but per the specifications one 
bank can choose to
-                         * return all the "Ntry" elements into one single ZIP 
entry, or even unzipped
-                         * at all.
-                         */
-                        response.orderData.unzipWithLoop {
-                            val fileName = it.first
-                            val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
-                            transaction {
-                                RawBankTransactionEntity.new {
-                                    sourceFileName = fileName
-                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
-                                    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)
-                                    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']")
-                                    debitorIban = 
camt53doc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
-                                    counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
-                                }
-                            }
-                        }
-                        call.respondText(
-                            "C53 data persisted into the database (WIP).",
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-            post("/ebics/subscribers/{id}/collect-transactions-c54") {
-                // FIXME(florian): Download C54 and store the result in the 
right database table
-            }
-            post("/ebics/subscribers/{id}/sendC52") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C52", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendCRZ") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "CRZ", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendC53") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.prettyPrintUnzip(),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-            }
-            post("/ebics/subscribers/{id}/sendC54") {
-                val id = expectId(call.parameters["id"])
-                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
-                val orderParams = paramsJson.toOrderParams()
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C54", orderParams)
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-            get("/ebics/subscribers/{id}/sendHTD") {
-                val customerIdAtNexus = expectId(call.parameters["id"])
-                val subscriberData = 
getSubscriberDetailsFromNexusUserId(customerIdAtNexus)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "HTD",
-                    EbicsStandardOrderParams()
-                )
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@get
-            }
-            post("/ebics/subscribers/{id}/sendHAA") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAA", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHVZ") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                // FIXME: order params are wrong
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVZ", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHVU") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                // FIXME: order params are wrong
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVU", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHPD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HPD", EbicsStandardOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
+            get("/ebics/subscribers/{id}/pubkeys") {
+                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
+                val response = transaction {
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
+                    val authPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray())
+                    val authPub = CryptoUtil.getRsaPublicFromPrivate(authPriv)
+                    val encPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray())
+                    val encPub = CryptoUtil.getRsaPublicFromPrivate(encPriv)
+                    val sigPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
+                    val sigPub = CryptoUtil.getRsaPublicFromPrivate(sigPriv)
+                    EbicsPubKeyInfo(
+                        bytesToBase64(authPub.encoded),
+                        bytesToBase64(encPub.encoded),
+                        bytesToBase64(sigPub.encoded)
+                    )
                 }
-                return@post
-            }
-
-            get("/ebics/subscribers/{id}/sendHKD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(
-                    client,
-                    subscriberData,
-                    "HKD",
-                    EbicsStandardOrderParams()
+                call.respond(
+                    HttpStatusCode.OK,
+                    response
                 )
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@get
-            }
-
-            post("/ebics/subscribers/{id}/sendTSD") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "TSD", EbicsGenericOrderParams())
-                when (response) {
-                    is EbicsDownloadSuccessResult -> {
-                        call.respondText(
-                            response.orderData.toString(Charsets.UTF_8),
-                            ContentType.Text.Plain,
-                            HttpStatusCode.OK
-                        )
-                    }
-                    is EbicsDownloadBankErrorResult -> {
-                        call.respond(
-                            HttpStatusCode.BadGateway,
-                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
-                        )
-                    }
-                }
-                return@post
             }
-
             get("/ebics/subscribers/{id}/keyletter") {
                 val nexusUserId = expectId(call.parameters["id"])
                 var usernameLine = "TODO"
@@ -732,7 +416,7 @@ fun main() {
                 var hostID = ""
                 transaction {
                     val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    val subscriber = nexusUser.ebicsSubscriber
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
                     val signPubTmp = CryptoUtil.getRsaPublicFromPrivate(
                         
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
                     )
@@ -834,231 +518,223 @@ fun main() {
                     HttpStatusCode.OK
                 )
             }
-            /**
-             * Lists the EBICS subscribers known to this service.
-             */
-            get("/ebics/subscribers") {
-                val ret = EbicsSubscribersResponseJson()
-                transaction {
-                    NexusUserEntity.all().forEach {
-                        ret.ebicsSubscribers.add(
-                            EbicsSubscriberInfoResponseJson(
-                                hostID = it.ebicsSubscriber.hostID,
-                                partnerID = it.ebicsSubscriber.partnerID,
-                                systemID = it.ebicsSubscriber.systemID,
-                                ebicsURL = it.ebicsSubscriber.ebicsURL,
-                                userID = it.ebicsSubscriber.userID,
-                                nexusUserID = it.id.value
-                            )
-                        )
-                    }
-                }
-                call.respond(ret)
-                return@get
-            }
 
-            /**
-             * Get all the details associated with a NEXUS user.
-             */
-            get("/ebics/subscribers/{id}") {
-                val nexusUserId = expectId(call.parameters["id"])
-                val response = transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusUserId)
-                    EbicsSubscriberInfoResponseJson(
-                        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)
-                return@get
-            }
 
-            get("/ebics/{id}/sendHev") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val request = makeEbicsHEVRequest(subscriberData)
-                val response = client.postToBank(subscriberData.ebicsUrl, 
request)
-                val versionDetails = parseEbicsHEVResponse(response)
-                call.respond(
-                    HttpStatusCode.OK,
-                    EbicsHevResponseJson(versionDetails.versions.map { 
ebicsVersionSpec ->
-                        ProtocolAndVersionJson(
-                            ebicsVersionSpec.protocol,
-                            ebicsVersionSpec.version
-                        )
-                    })
-                )
-                return@get
-            }
+            /** STATE CHANGES VIA EBICS */
 
-            /**
-             * 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)
-                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
-                        }
-                    }
-                }
-                call.respondText(
-                    "New NEXUS user registered. ID: $newUserId",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendIni") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val iniRequest = makeEbicsIniRequest(subscriberData)
-                val responseStr = client.postToBank(
-                    subscriberData.ebicsUrl,
-                    iniRequest
-                )
-                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
-                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
-                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected INI response code: 
${resp.technicalReturnCode}")
+            post("/ebics/admin/execute-payments") {
+                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
+                    val entity = Pain001Entity.find {
+                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
+                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
+                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
                 }
-                call.respondText("Bank accepted signature key\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
-                return@post
-            }
-
-            post("/ebics/subscribers/{id}/sendHia") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val hiaRequest = makeEbicsHiaRequest(subscriberData)
-                val responseStr = client.postToBank(
-                    subscriberData.ebicsUrl,
-                    hiaRequest
+                logger.debug("Uploading PAIN.001: ${painDoc}")
+                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
+                doEbicsUploadTransaction(
+                    client,
+                    subscriberDetails,
+                    "CCT",
+                    painDoc.toByteArray(Charsets.UTF_8),
+                    EbicsStandardOrderParams()
                 )
-                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
-                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
-                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected HIA response code: 
${resp.technicalReturnCode}")
+                /* flow here == no errors occurred */
+                transaction {
+                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
+                        HttpStatusCode.InternalServerError,
+                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
+                    )
+                    payment.submitted = true
                 }
                 call.respondText(
-                    "Bank accepted authentication and encryption keys\n",
+                    "CCT message submitted to the bank",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
-
-            post("/ebics/subscribers/{id}/restoreBackup") {
-                val body = call.receive<EbicsKeysBackupJson>()
-                val nexusId = expectId(call.parameters["id"])
-                val subscriber = transaction {
-                    NexusUserEntity.findById(nexusId)
-                }
-                if (subscriber != null) {
-                    call.respond(
-                        HttpStatusCode.Conflict,
-                        NexusErrorJson("ID exists, please choose a new one")
-                    )
-                    return@post
-                }
-                val (authKey, encKey, sigKey) = try {
-                    Triple(
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.authBlob)), body.passphrase!!
-                        ),
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.encBlob)), body.passphrase
-                        ),
-                        CryptoUtil.decryptKey(
-                            
EncryptedPrivateKeyInfo(base64ToBytes(body.sigBlob)), body.passphrase
-                        )
-                    )
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                    logger.info("Restoring keys failed, probably due to wrong 
passphrase")
-                    throw NexusError(HttpStatusCode.BadRequest, reason = "Bad 
backup given")
+            post("/ebics/admin/execute-payments-ccc") {
+                val (paymentRowId, painDoc: String, debtorAccount) = 
transaction {
+                    val entity = Pain001Entity.find {
+                        (Pain001Table.submitted eq false) and 
(Pain001Table.invalid eq false)
+                    }.firstOrNull() ?: throw 
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
+                    Triple(entity.id, createPain001document(entity), 
entity.debtorAccount)
                 }
-                logger.info("Restoring keys, creating new user: $nexusId")
-                try {
-                    transaction {
-                        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 into database"))
-                    return@post
+                logger.debug("Uploading PAIN.001 via CCC: ${painDoc}")
+                val subscriberDetails = 
getSubscriberDetailsFromBankAccount(debtorAccount)
+                doEbicsUploadTransaction(
+                    client,
+                    subscriberDetails,
+                    "CCC",
+                    painDoc.toByteArray(Charsets.UTF_8).zip(),
+                    EbicsStandardOrderParams()
+                )
+                /* flow here == no errors occurred */
+                transaction {
+                    val payment = Pain001Entity.findById(paymentRowId) ?: 
throw NexusError(
+                        HttpStatusCode.InternalServerError,
+                        "Severe internal error: could not find payment in DB 
after having submitted it to the bank"
+                    )
+                    payment.submitted = true
                 }
                 call.respondText(
-                    "Keys successfully restored",
+                    "CCC message submitted to the bank",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
                 return@post
             }
+            post("/ebics/subscribers/{id}/collect-transactions-c52") {
+                // FIXME(florian): Download C52 and store the result in the 
right database table
 
-            get("/ebics/subscribers/{id}/pubkeys") {
-                val nexusId = expectId(call.parameters["id"])
+            }
+            /** exports keys backup copy */
+            post("/ebics/subscribers/{id}/backup") {
+                val body = call.receive<EbicsBackupRequestJson>()
                 val response = transaction {
-                    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())
-                    val encPub = CryptoUtil.getRsaPublicFromPrivate(encPriv)
-                    val sigPriv = 
CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray())
-                    val sigPub = CryptoUtil.getRsaPublicFromPrivate(sigPriv)
-                    EbicsPubKeyInfo(
-                        bytesToBase64(authPub.encoded),
-                        bytesToBase64(encPub.encoded),
-                        bytesToBase64(sigPub.encoded)
+                    val nexusUser = 
expectNexusIdTransaction(call.parameters["id"])
+                    val subscriber = getEbicsSubscriberFromUser(nexusUser)
+                    EbicsKeysBackupJson(
+                        userID = subscriber.userID,
+                        hostID = subscriber.hostID,
+                        partnerID = subscriber.partnerID,
+                        ebicsURL = subscriber.ebicsURL,
+                        authBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                
subscriber.authenticationPrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        ),
+                        encBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                subscriber.encryptionPrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        ),
+                        sigBlob = bytesToBase64(
+                            CryptoUtil.encryptKey(
+                                subscriber.signaturePrivateKey.toByteArray(),
+                                body.passphrase
+                            )
+                        )
                     )
                 }
+                call.response.headers.append("Content-Disposition", 
"attachment")
                 call.respond(
                     HttpStatusCode.OK,
                     response
                 )
             }
+
+            /** Download keys from bank */
+            post("/ebics/subscribers/{id}/sync") {
+                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
+                val subscriberDetails = 
getSubscriberDetailsFromNexusUserId(nexusUser.id.value)
+                val hpbRequest = makeEbicsHpbRequest(subscriberDetails)
+                val responseStr = 
client.postToBank(subscriberDetails.ebicsUrl, hpbRequest)
+                val response = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, responseStr)
+                val orderData = response.orderData ?: throw NexusError(
+                    HttpStatusCode.InternalServerError,
+                    "HPB response has no order data"
+                )
+                val hpbData = parseEbicsHpbOrder(orderData)
+                // put bank's keys into database.
+                transaction {
+                    val ebicsSubscriber = getEbicsSubscriberFromUser(nexusUser)
+                    ebicsSubscriber.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
+                    ebicsSubscriber.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
+                }
+                call.respondText("Bank keys stored in database\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
+                return@post
+            }
+            post("/ebics/subscribers/{id}/fetch-payment-status") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val response = doEbicsDownloadTransaction(
+                    client,
+                    subscriberData,
+                    "CRZ",
+                    orderParams
+                )
+                when (response) {
+                    is EbicsDownloadSuccessResult ->
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    /**
+                     * NOTE: flow gets here when the bank-technical return 
code is
+                     * different from 000000.  This happens also for 090005 
(no data available)
+                     */
+                    else -> call.respond(NexusErrorJson("Could not download 
any PAIN.002"))
+                }
+                return@post
+            }
+            post("/ebics/subscribers/{id}/collect-transactions-c53") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                when (val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)) {
+                    is EbicsDownloadSuccessResult -> {
+                        /**
+                         * The current code is _heavily_ dependent on the way 
GLS returns
+                         * data.  For example, GLS makes one ZIP entry for 
each "Ntry" element
+                         * (a bank transfer), but per the specifications one 
bank can choose to
+                         * return all the "Ntry" elements into one single ZIP 
entry, or even unzipped
+                         * at all.
+                         */
+                        response.orderData.unzipWithLoop {
+                            val fileName = it.first
+                            val camt53doc = 
XMLUtil.parseStringIntoDom(it.second)
+                            transaction {
+                                RawBankTransactionEntity.new {
+                                    sourceFileName = fileName
+                                    unstructuredRemittanceInformation = 
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
+                                    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)
+                                    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']")
+                                    debitorIban = 
camt53doc.pickString("//*[local-name()='DbtrAcct']//*[local-name()='IBAN']")
+                                    counterpartBic = 
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
+                                }
+                            }
+                        }
+                        call.respondText(
+                            "C53 data persisted into the database (WIP).",
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
+                return@post
+            }
+            post("/ebics/subscribers/{id}/collect-transactions-c54") {
+                // FIXME(florian): Download C54 and store the result in the 
right database table
+            }
             /**
              * This endpoint downloads bank account information associated 
with the
              * calling EBICS subscriber.
              */
             post("/ebics/subscribers/{id}/fetch-accounts") {
-                val nexusUserId = expectId(call.parameters["id"])
+                val nexusUser = 
expectNexusIdTransaction((call.parameters["id"]))
                 val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
                 val orderParams = paramsJson.toOrderParams()
-                val subscriberData = 
getSubscriberDetailsFromNexusUserId(nexusUserId)
+                val subscriberData = 
getSubscriberDetailsFromNexusUserId(nexusUser.id.value)
                 val response = doEbicsDownloadTransaction(client, 
subscriberData, "HTD", orderParams)
                 when (response) {
                     is EbicsDownloadSuccessResult -> {
@@ -1070,9 +746,8 @@ fun main() {
                                     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
+                                    ebicsSubscriber = 
getEbicsSubscriberFromUser(nexusUser)
                                     this.bankAccount = bankAccount
                                 }
                             }
@@ -1094,89 +769,98 @@ fun main() {
                 return@post
             }
 
-            /* performs a keys backup */
-            post("/ebics/subscribers/{id}/backup") {
-                val nexusId = expectId(call.parameters["id"])
-                val body = call.receive<EbicsBackupRequestJson>()
-                val response = transaction {
-                    val nexusUser = expectNexusIdTransaction(nexusId)
-                    val subscriber = nexusUser.ebicsSubscriber
-                    EbicsKeysBackupJson(
-                        userID = subscriber.userID,
-                        hostID = subscriber.hostID,
-                        partnerID = subscriber.partnerID,
-                        ebicsURL = subscriber.ebicsURL,
-                        authBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                
subscriber.authenticationPrivateKey.toByteArray(),
-                                body.passphrase
-                            )
-                        ),
-                        encBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                subscriber.encryptionPrivateKey.toByteArray(),
-                                body.passphrase
-                            )
-                        ),
-                        sigBlob = bytesToBase64(
-                            CryptoUtil.encryptKey(
-                                subscriber.signaturePrivateKey.toByteArray(),
-                                body.passphrase
-                            )
+            /** EBICS MESSAGES / DEBUG */
+
+            // FIXME: some messages include a ZIPped payload.
+            post("/ebics/subscribers/{id}/send{MSG}") {
+                val id = expectId(call.parameters["id"])
+                val MSG = expectId(call.parameters["MSG"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                println("$MSG order params: $orderParams")
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val response = doEbicsDownloadTransaction(
+                    client,
+                    subscriberData,
+                    MSG,
+                    orderParams
+                )
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
                         )
-                    )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
                 }
-                call.response.headers.append("Content-Disposition", 
"attachment")
+                return@post
+            }
+            get("/ebics/{id}/sendHEV") {
+                val id = expectId(call.parameters["id"])
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val request = makeEbicsHEVRequest(subscriberData)
+                val response = client.postToBank(subscriberData.ebicsUrl, 
request)
+                val versionDetails = parseEbicsHEVResponse(response)
                 call.respond(
                     HttpStatusCode.OK,
-                    response
+                    EbicsHevResponseJson(versionDetails.versions.map { 
ebicsVersionSpec ->
+                        ProtocolAndVersionJson(
+                            ebicsVersionSpec.protocol,
+                            ebicsVersionSpec.version
+                        )
+                    })
+                )
+                return@get
+            }
+            post("/ebics/subscribers/{id}/sendINI") {
+                val id = expectId(call.parameters["id"])
+                val subscriberData = getSubscriberDetailsFromNexusUserId(id)
+                val iniRequest = makeEbicsIniRequest(subscriberData)
+                val responseStr = client.postToBank(
+                    subscriberData.ebicsUrl,
+                    iniRequest
                 )
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected INI response code: 
${resp.technicalReturnCode}")
+                }
+                call.respondText("Bank accepted signature key\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
+                return@post
             }
 
-            post("/ebics/subscribers/{id}/sendTSU") {
+            post("/ebics/subscribers/{id}/sendHIA") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromNexusUserId(id)
-                val payload = "PAYLOAD"
-                doEbicsUploadTransaction(
-                    client,
-                    subscriberData,
-                    "TSU",
-                    payload.toByteArray(Charsets.UTF_8),
-                    EbicsGenericOrderParams()
+                val hiaRequest = makeEbicsHiaRequest(subscriberData)
+                val responseStr = client.postToBank(
+                    subscriberData.ebicsUrl,
+                    hiaRequest
                 )
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw 
NexusError(HttpStatusCode.InternalServerError,"Unexpected HIA response code: 
${resp.technicalReturnCode}")
+                }
                 call.respondText(
-                    "TST INITIALIZATION & TRANSACTION phases succeeded\n",
+                    "Bank accepted authentication and encryption keys\n",
                     ContentType.Text.Plain,
                     HttpStatusCode.OK
                 )
-            }
-
-            post("/ebics/subscribers/{id}/sync") {
-                val nexusId = expectId(call.parameters["id"])
-                val subscriberDetails = 
getSubscriberDetailsFromNexusUserId(nexusId)
-                val hpbRequest = makeEbicsHpbRequest(subscriberDetails)
-                val responseStr = 
client.postToBank(subscriberDetails.ebicsUrl, hpbRequest)
-
-                val response = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, responseStr)
-                val orderData =
-                    response.orderData ?: throw 
NexusError(HttpStatusCode.InternalServerError, "HPB response has no order data")
-                val hpbData = parseEbicsHpbOrder(orderData)
-
-                // put bank's keys into database.
-                transaction {
-                    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
-            }
-            post("/test/intercept") {
-                call.respondText(call.receive<String>() + "\n")
                 return@post
             }
+
+            /** PLUGINS */
+            /* Taler class will initialize all the relevant handlers.  */
+            Taler(this)
         }
     }
+
     logger.info("Up and running")
     server.start(wait = true)
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 7e6c7d9..b942ac8 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -284,7 +284,7 @@ class Taler(app: Route) {
                         creditorIban = creditorObj.iban
                         counterpartBic = creditorObj.bic
                         bookingDate = DateTime.now().millis
-                        nexusSubscriber = nexusUser.ebicsSubscriber
+                        nexusSubscriber = getEbicsSubscriberFromUser(nexusUser)
                         status = "BOOK"
                     }
                 } else null
@@ -370,8 +370,7 @@ class Taler(app: Route) {
          * all the prepared payments.  */
         
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
             transaction {
-                val nexusUser = expectNexusIdTransaction(call.parameters["id"])
-                val subscriber = nexusUser.ebicsSubscriber
+                val subscriber = 
getSubscriberEntityFromNexusUserId(call.parameters["id"])
                 val acctid = expectAcctidTransaction(call.parameters["acctid"])
                 if (!subscriberHasRights(subscriber, acctid)) {
                     throw NexusError(
@@ -549,7 +548,7 @@ class Taler(app: Route) {
                         val nexusUser = expectNexusIdTransaction(exchangeId)
                         EbicsToBankAccountEntity.new {
                             bankAccount = newBankAccount
-                            ebicsSubscriber = nexusUser.ebicsSubscriber
+                            ebicsSubscriber = 
getEbicsSubscriberFromUser(nexusUser)
                         }
                     }
                 }
diff --git a/nexus/src/test/kotlin/authentication.kt 
b/nexus/src/test/kotlin/authentication.kt
index 2e9c5be..7cbb36d 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -17,21 +17,11 @@ class AuthenticationTest {
             SchemaUtils.create(NexusUsersTable)
             NexusUserEntity.new(id = "username") {
                 password = SerialBlob(CryptoUtil.hashStringSHA256("password"))
-                ebicsSubscriber = EbicsSubscriberEntity.new {
-                    ebicsURL = "ebics url"
-                    hostID = "host"
-                    partnerID = "partner"
-                    userID = "user"
-                    systemID = "system"
-                    signaturePrivateKey = 
SerialBlob("signturePrivateKey".toByteArray())
-                    authenticationPrivateKey = 
SerialBlob("authenticationPrivateKey".toByteArray())
-                    encryptionPrivateKey = 
SerialBlob("encryptionPrivateKey".toByteArray())
-                }
             }
             // base64 of "username:password" == "dXNlcm5hbWU6cGFzc3dvcmQ="
-            val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword(
+            val hashedPass= extractUserAndHashedPassword(
                 "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
-            )
+            ).second
             val row = NexusUserEntity.findById("username")
             assert(row?.password == SerialBlob(hashedPass))
         }
@@ -39,7 +29,9 @@ class AuthenticationTest {
 
     @Test
     fun basicAuthHeaderTest() {
-        val (username: String, hashedPass: ByteArray) = 
extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")
+        val hashedPass = extractUserAndHashedPassword(
+            "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
+        ).second
         
assert(CryptoUtil.hashStringSHA256("password").contentEquals(hashedPass))
     }
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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