commit e5e2ba20a612aaebeb3365f6d07640abb22f8d45
parent 55a63c220d9af923aa986138f27bdcea7be36768
Author: Marcello Stanisci <ms@taler.net>
Date: Tue, 28 Apr 2020 18:12:54 +0200
More separation for transport types.
Diffstat:
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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