[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.