libeufin

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

commit 437729559aac3ccda0082e8f623454f26a554537
parent 38ada6a6d3f64c355ea857d59c9f459426cf70a7
Author: Florian Dold <florian.dold@gmail.com>
Date:   Sun, 24 May 2020 20:59:48 +0530

implement generic /connect

Diffstat:
Aintegration-tests/test-ebics-highlevel.py | 288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 5+++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt | 6+++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 5+++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mutil/src/main/kotlin/Ebics.kt | 35+++++++++++++++++++++--------------
Mutil/src/main/kotlin/ParametersChecks.kt | 8++++----
Mutil/src/main/kotlin/XMLUtil.kt | 5++---
Mutil/src/main/kotlin/strings.kt | 2+-
9 files changed, 397 insertions(+), 35 deletions(-)

diff --git a/integration-tests/test-ebics-highlevel.py b/integration-tests/test-ebics-highlevel.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 + +from requests import post, get +from subprocess import call, Popen, PIPE +from time import sleep +import os +import socket +import hashlib +import base64 + +# Steps implemented in this test. +# +# 0 Prepare sandbox. +# -> (a) Make a EBICS host, (b) make a EBICS subscriber +# for the test runner, and (c) assign a IBAN to such +# subscriber. +# +# 1 Prepare nexus. +# -> (a) Make a Nexus user, (b) make a EBICS subscriber +# associated to that user +# +# 2 Prepare the Ebics bank connection for the nexus user. +# -> (a) Upload keys from Nexus to the Bank (INI & HIA), +# (b) Download key from the Bank (HPB) to the Nexus, +# and (c) Fetch the bank account owned by that subscriber +# at the bank. + +# 3 Request history from the Nexus to the Bank (C53). +# 4 Verify that history is empty. +# 5 Issue a payment from Nexus +# -> (a) Prepare & (b) trigger CCT. +# 6 Request history after submitting the payment, +# from Nexus to Bank. +# 7 Verify that previous payment shows up. + +# Nexus user details +USERNAME = "person" +PASSWORD = "y" +USER_AUTHORIZATION_HEADER = "basic {}".format( + base64.b64encode(b"person:y").decode("utf-8") +) + +# Admin authentication +ADMIN_AUTHORIZATION_HEADER = "basic {}".format( + base64.b64encode(b"admin:x").decode("utf-8") +) + +# EBICS details +EBICS_URL = "http://localhost:5000/ebicsweb" +HOST_ID = "HOST01" +PARTNER_ID = "PARTNER1" +USER_ID = "USER1" +EBICS_VERSION = "H004" + +# Subscriber's bank account +SUBSCRIBER_IBAN = "GB33BUKB20201555555555" +SUBSCRIBER_BIC = "BUKBGB22" +SUBSCRIBER_NAME = "Oliver Smith" +BANK_ACCOUNT_LABEL = "savings" + + +def fail(msg): + print(msg) + nexus.terminate() + sandbox.terminate() + exit(1) + + +def checkPorts(ports): + for i in ports: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind(("0.0.0.0", i)) + s.close() + except: + print("Port {} is not available".format(i)) + exit(77) + + +def assertResponse(response): + if response.status_code != 200: + print("Test failed on URL: {}".format(response.url)) + # stdout/stderr from both services is A LOT of text. + # Confusing to dump all that to console. + print("Check nexus.log and sandbox.log, probably under /tmp") + nexus.terminate() + sandbox.terminate() + exit(1) + # Allows for finer grained checks. + return response + + +# -1 Clean databases and start services. +os.chdir("..") +assert 0 == call(["rm", "-f", "sandbox/libeufin-sandbox.sqlite3"]) +assert 0 == call(["rm", "-f", "nexus/libeufin-nexus.sqlite3"]) +DEVNULL = open(os.devnull, "w") + +assert 0 == call( + ["./gradlew", "nexus:run", "--console=plain", "--args=superuser admin --password x"] +) + +# Start nexus +checkPorts([5001]) +nexus = Popen( + ["./gradlew", "nexus:run", "--console=plain", "--args=serve"], + stdout=PIPE, + stderr=PIPE, +) +for i in range(10): + try: + get("http://localhost:5001/") + except: + if i == 9: + nexus.terminate() + stdout, stderr = nexus.communicate() + print("Nexus timed out") + print("{}\n{}".format(stdout.decode(), stderr.decode())) + exit(77) + sleep(2) + continue + break +# Start sandbox +checkPorts([5000]) +sandbox = Popen(["./gradlew", "sandbox:run"], stdout=PIPE, stderr=PIPE) +for i in range(10): + try: + get("http://localhost:5000/") + except: + if i == 9: + nexus.terminate() + sandbox.terminate() + stdout, stderr = nexus.communicate() + print("Sandbox timed out") + print("{}\n{}".format(stdout.decode(), stderr.decode())) + exit(77) + sleep(2) + continue + break + +# 0.a +assertResponse( + post( + "http://localhost:5000/admin/ebics/host", + json=dict(hostID=HOST_ID, ebicsVersion=EBICS_VERSION), + ) +) + +# 0.b +assertResponse( + post( + "http://localhost:5000/admin/ebics/subscribers", + json=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID), + ) +) + +# 0.c +assertResponse( + post( + "http://localhost:5000/admin/ebics/bank-accounts", + json=dict( + subscriber=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID), + iban=SUBSCRIBER_IBAN, + bic=SUBSCRIBER_BIC, + name=SUBSCRIBER_NAME, + label=BANK_ACCOUNT_LABEL, + ), + ) +) + +# 1.a, make a new nexus user. + +assertResponse( + post( + "http://localhost:5001/users", + headers=dict(Authorization=ADMIN_AUTHORIZATION_HEADER), + json=dict(username=USERNAME, password=PASSWORD), + ) +) + +print("creating bank connection") + +# 1.b, make a ebics bank connection for the new user. +assertResponse( + post( + "http://localhost:5001/bank-connections", + json=dict( + name="my-ebics", + source="new", + type="ebics", + data=dict( + ebicsURL=EBICS_URL, hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID + ), + ), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +print("connecting") + +assertResponse( + post( + "http://localhost:5001/bank-connections/my-ebics/connect", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + + +# 2.c, fetch bank account information +assertResponse( + post( + "http://localhost:5001/bank-connections/my-ebics/ebics/import-accounts", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +# 3, ask nexus to download history +assertResponse( + post( + f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +# 4, make sure history is empty +resp = assertResponse( + get( + f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) +if len(resp.json().get("transactions")) != 0: + fail("unexpected number of transactions") + +# 5.a, prepare a payment +resp = assertResponse( + post( + "http://localhost:5001/bank-accounts/{}/prepared-payments".format( + BANK_ACCOUNT_LABEL + ), + json=dict( + iban="FR7630006000011234567890189", + bic="AGRIFRPP", + name="Jacques La Fayette", + subject="integration test", + amount="EUR:1", + ), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) +PREPARED_PAYMENT_UUID = resp.json().get("uuid") +if PREPARED_PAYMENT_UUID == None: + fail("Payment UUID not received") + +# 5.b, submit prepared statement +assertResponse( + post( + f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/prepared-payments/{PREPARED_PAYMENT_UUID}/submit", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +# 6, request history after payment submission +assertResponse( + post( + f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions", + json=dict(), + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +resp = assertResponse( + get( + f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions", + headers=dict(Authorization=USER_AUTHORIZATION_HEADER), + ) +) + +if len(resp.json().get("transactions")) != 1: + fail("Unexpected number of transactions; should be 1") + +nexus.terminate() +sandbox.terminate() +print("Test passed!") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -8,6 +8,7 @@ import org.jetbrains.exposed.sql.StdOutSqlLogger import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction +import tech.libeufin.util.EbicsInitState import tech.libeufin.util.amount import java.sql.Connection @@ -202,6 +203,8 @@ object EbicsSubscribersTable : IntIdTable() { val bankEncryptionPublicKey = blob("bankEncryptionPublicKey").nullable() val bankAuthenticationPublicKey = blob("bankAuthenticationPublicKey").nullable() val nexusBankConnection = reference("nexusBankConnection", NexusBankConnectionsTable) + val ebicsIniState = enumerationByName("ebicsIniState", 16, EbicsInitState::class) + val ebicsHiaState = enumerationByName("ebicsHiaState", 16, EbicsInitState::class) } class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { @@ -218,6 +221,8 @@ class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) { var bankEncryptionPublicKey by EbicsSubscribersTable.bankEncryptionPublicKey var bankAuthenticationPublicKey by EbicsSubscribersTable.bankAuthenticationPublicKey var nexusBankConnection by NexusBankConnectionEntity referencedOn EbicsSubscribersTable.nexusBankConnection + var ebicsIniState by EbicsSubscribersTable.ebicsIniState + var ebicsHiaState by EbicsSubscribersTable.ebicsHiaState } object NexusUsersTable : IdTable<String>() { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt @@ -51,7 +51,7 @@ suspend fun doEbicsDownloadTransaction( // Initialization phase val initDownloadRequestStr = createEbicsRequestForDownloadInitialization(subscriberDetails, orderType, orderParams) - val payloadChunks = LinkedList<String>(); + val payloadChunks = LinkedList<String>() val initResponseStr = client.postToBank(subscriberDetails.ebicsUrl, initDownloadRequestStr) val initResponse = parseAndValidateEbicsResponse(subscriberDetails, initResponseStr) @@ -211,8 +211,8 @@ suspend fun doEbicsHpbRequest( request ) val parsedResponse = parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, respStr) - val orderData = parsedResponse.orderData ?: throw NexusError( - HttpStatusCode.InternalServerError, + val orderData = parsedResponse.orderData ?: throw EbicsProtocolError( + HttpStatusCode.BadGateway, "Cannot find data in a HPB response" ) return parseEbicsHpbOrder(orderData) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt @@ -1,6 +1,5 @@ package tech.libeufin.nexus -import io.ktor.application.ApplicationCall import io.ktor.client.HttpClient import io.ktor.http.HttpStatusCode import io.ktor.request.ApplicationRequest @@ -82,7 +81,9 @@ fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity): EbicsC customerSignPriv = CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()), customerAuthPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()), - customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()) + customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()), + ebicsIniState = subscriber.ebicsIniState, + ebicsHiaState = subscriber.ebicsHiaState ) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -173,6 +173,8 @@ fun createEbicsBankConnectionFromBackup( encryptionPrivateKey = SerialBlob(encKey.encoded) authenticationPrivateKey = SerialBlob(authKey.encoded) nexusBankConnection = bankConn + ebicsIniState = EbicsInitState.UNKNOWN + ebicsHiaState = EbicsInitState.UNKNOWN } } catch (e: Exception) { throw NexusError( @@ -188,20 +190,22 @@ fun createEbicsBankConnection(bankConnectionName: String, user: NexusUserEntity, owner = user type = "ebics" } - val data = jacksonObjectMapper().treeToValue(data, EbicsNewTransport::class.java) + val newTransportData = jacksonObjectMapper().treeToValue(data, EbicsNewTransport::class.java) val pairA = CryptoUtil.generateRsaKeyPair(2048) val pairB = CryptoUtil.generateRsaKeyPair(2048) val pairC = CryptoUtil.generateRsaKeyPair(2048) EbicsSubscriberEntity.new { - ebicsURL = data.ebicsURL - hostID = data.hostID - partnerID = data.partnerID - userID = data.userID - systemID = data.systemID + ebicsURL = newTransportData.ebicsURL + hostID = newTransportData.hostID + partnerID = newTransportData.partnerID + userID = newTransportData.userID + systemID = newTransportData.systemID signaturePrivateKey = SerialBlob(pairA.private.encoded) encryptionPrivateKey = SerialBlob(pairB.private.encoded) authenticationPrivateKey = SerialBlob(pairC.private.encoded) nexusBankConnection = bankConn + ebicsIniState = EbicsInitState.NOT_SENT + ebicsHiaState = EbicsInitState.NOT_SENT } } @@ -251,7 +255,7 @@ fun serverMain() { cause.statusCode ) } - exception<UtilError> { cause -> + exception<EbicsProtocolError> { cause -> logger.error("Exception while handling '${call.request.uri}'", cause) call.respondText( cause.reason, @@ -704,7 +708,65 @@ fun serverMain() { } post("/bank-connections/{connid}/connect") { - throw NotImplementedError() + val subscriber = transaction { + val user = authenticateRequest(call.request) + val conn = requireBankConnection(call, "connid") + if (conn.type != "ebics") { + throw NexusError( + HttpStatusCode.BadRequest, + "bank connection is not of type 'ebics' (but '${conn.type}')" + ) + } + getEbicsSubscriberDetails(user.id.value, conn.id.value) + } + if (subscriber.bankAuthPub != null && subscriber.bankEncPub != null) { + call.respond(object { + val ready = true + }) + return@post + } + + val iniDone = when (subscriber.ebicsIniState) { + EbicsInitState.NOT_SENT, EbicsInitState.UNKNOWN -> { + val iniResp = doEbicsIniRequest(client, subscriber) + iniResp.bankReturnCode == EbicsReturnCode.EBICS_OK && iniResp.technicalReturnCode == EbicsReturnCode.EBICS_OK + } + else -> { + false + } + } + val hiaDone = when (subscriber.ebicsHiaState) { + EbicsInitState.NOT_SENT, EbicsInitState.UNKNOWN -> { + val hiaResp = doEbicsHiaRequest(client, subscriber) + hiaResp.bankReturnCode == EbicsReturnCode.EBICS_OK && hiaResp.technicalReturnCode == EbicsReturnCode.EBICS_OK + } + else -> { + false + } + } + + val hpbData = try { + doEbicsHpbRequest(client, subscriber) + } catch (e: EbicsProtocolError) { + logger.warn("failed hpb request", e) + null + } + transaction { + val conn = requireBankConnection(call, "connid") + val subscriberEntity = + EbicsSubscriberEntity.find { EbicsSubscribersTable.nexusBankConnection eq conn.id }.first() + if (iniDone) { + subscriberEntity.ebicsIniState = EbicsInitState.SENT + } + if (hiaDone) { + subscriberEntity.ebicsHiaState = EbicsInitState.SENT + } + if (hpbData != null) { + subscriberEntity.bankAuthenticationPublicKey = SerialBlob(hpbData.authenticationPubKey.encoded) + subscriberEntity.bankEncryptionPublicKey = SerialBlob(hpbData.encryptionPubKey.encoded) + } + } + call.respond(object {}) } post("/bank-connections/{connid}/ebics/send-ini") { diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt @@ -38,7 +38,8 @@ import java.util.* import java.util.zip.DeflaterInputStream import javax.xml.datatype.DatatypeFactory -data class UtilError(val statusCode: HttpStatusCode, val reason: String) : Exception() +data class EbicsProtocolError(val statusCode: HttpStatusCode, val reason: String) : Exception(reason) + data class EbicsDateRange(val start: LocalDate, val end: LocalDate) sealed class EbicsOrderParams @@ -51,6 +52,10 @@ data class EbicsGenericOrderParams( val params: Map<String, String> = mapOf() ) : EbicsOrderParams() +enum class EbicsInitState { + SENT, NOT_SENT, UNKNOWN +} + /** * This class is a mere container that keeps data found * in the database and that is further needed to sign / verify @@ -66,7 +71,9 @@ data class EbicsClientSubscriberDetails( val hostId: String, val customerEncPriv: RSAPrivateCrtKey, val customerAuthPriv: RSAPrivateCrtKey, - val customerSignPriv: RSAPrivateCrtKey + val customerSignPriv: RSAPrivateCrtKey, + val ebicsIniState: EbicsInitState, + val ebicsHiaState: EbicsInitState ) /** @@ -237,11 +244,11 @@ fun createEbicsRequestForDownloadInitialization( subscriberDetails.hostId, getNonce(128), DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()), - subscriberDetails.bankEncPub ?: throw UtilError( + subscriberDetails.bankEncPub ?: throw EbicsProtocolError( HttpStatusCode.BadRequest, "Invalid subscriber state 'bankEncPub' missing, please send HPB first" ), - subscriberDetails.bankAuthPub ?: throw UtilError( + subscriberDetails.bankAuthPub ?: throw EbicsProtocolError( HttpStatusCode.BadRequest, "Invalid subscriber state 'bankAuthPub' missing, please send HPB first" ), @@ -312,7 +319,7 @@ enum class EbicsReturnCode(val errorCode: String) { return x } } - throw UtilError(HttpStatusCode.InternalServerError, "Unknown EBICS status code: $errorCode") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Unknown EBICS status code: $errorCode") } } } @@ -338,14 +345,14 @@ fun parseAndDecryptEbicsKeyManagementResponse( val resp = try { XMLUtil.convertStringToJaxb<EbicsKeyManagementResponse>(responseStr) } catch (e: Exception) { - throw UtilError(HttpStatusCode.InternalServerError, "Invalid XML received from bank") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML received from bank") } val retCode = EbicsReturnCode.lookup(resp.value.header.mutable.returnCode) val daeXml = resp.value.body.dataTransfer?.dataEncryptionInfo val orderData = if (daeXml != null) { val dae = DataEncryptionInfo(daeXml.transactionKey, daeXml.encryptionPubKeyDigest.value) - val encOrderData = resp.value.body.dataTransfer?.orderData?.value ?: throw UtilError( + val encOrderData = resp.value.body.dataTransfer?.orderData?.value ?: throw EbicsProtocolError( HttpStatusCode.InternalServerError, "Invalid XML/orderData received from bank" ) decryptAndDecompressResponse(subscriberDetails, dae, listOf(encOrderData)) @@ -371,7 +378,7 @@ fun parseEbicsHpbOrder(orderDataRaw: ByteArray): HpbResponseData { val resp = try { XMLUtil.convertStringToJaxb<HPBResponseOrderData>(orderDataRaw.toString(Charsets.UTF_8)) } catch (e: Exception) { - throw UtilError(HttpStatusCode.InternalServerError, "Invalid XML (as HPB response) received from bank") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML (as HPB response) received from bank") } val encPubKey = CryptoUtil.loadRsaPublicKeyFromComponents( resp.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, @@ -397,23 +404,23 @@ fun parseAndValidateEbicsResponse( val responseDocument = try { XMLUtil.parseStringIntoDom(responseStr) } catch (e: Exception) { - throw UtilError(HttpStatusCode.InternalServerError, "Invalid XML (as EbicsResponse) received from bank") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML (as EbicsResponse) received from bank") } if (!XMLUtil.verifyEbicsDocument( responseDocument, - subscriberDetails.bankAuthPub ?: throw UtilError( + subscriberDetails.bankAuthPub ?: throw EbicsProtocolError( HttpStatusCode.BadRequest, "Invalid subscriber state: bankAuthPub missing, please send HPB first" ) ) ) { - throw UtilError(HttpStatusCode.InternalServerError, "Bank's signature validation failed") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Bank's signature validation failed") } val resp = try { XMLUtil.convertStringToJaxb<EbicsResponse>(responseStr) } catch (e: Exception) { - throw UtilError(HttpStatusCode.InternalServerError, "Could not transform string-response from bank into JAXB") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Could not transform string-response from bank into JAXB") } val bankReturnCodeStr = resp.value.body.returnCode.value @@ -452,7 +459,7 @@ fun getDecryptionKey(subscriberDetails: EbicsClientSubscriberDetails, pubDigest: if (pubDigest.contentEquals(encPubDigest)) { return subscriberDetails.customerEncPriv } - throw UtilError(HttpStatusCode.NotFound, "Could not find customer's public key") + throw EbicsProtocolError(HttpStatusCode.NotFound, "Could not find customer's public key") } /** @@ -512,7 +519,7 @@ fun parseEbicsHEVResponse(respStr: String): EbicsHevDetails { XMLUtil.convertStringToJaxb<HEVResponse>(respStr) } catch (e: Exception) { logger.error("Exception while parsing HEV response", e) - throw UtilError(HttpStatusCode.InternalServerError, "Invalid HEV received from bank") + throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid HEV received from bank") } val versions = resp.value.versionNumber.map { versionNumber -> EbicsVersionSpec(versionNumber.protocolVersion, versionNumber.value) diff --git a/util/src/main/kotlin/ParametersChecks.kt b/util/src/main/kotlin/ParametersChecks.kt @@ -7,12 +7,12 @@ fun expectInt(param: String): Int { return try { param.toInt() } catch (e: Exception) { - throw UtilError(HttpStatusCode.BadRequest,"'$param' is not Int") + throw EbicsProtocolError(HttpStatusCode.BadRequest,"'$param' is not Int") } } fun <T>expectNonNull(param: T?): T { - return param ?: throw UtilError( + return param ?: throw EbicsProtocolError( HttpStatusCode.BadRequest, "Non-null value expected." ) @@ -22,7 +22,7 @@ fun expectLong(param: String): Long { return try { param.toLong() } catch (e: Exception) { - throw UtilError(HttpStatusCode.BadRequest,"'$param' is not Long") + throw EbicsProtocolError(HttpStatusCode.BadRequest,"'$param' is not Long") } } @@ -36,5 +36,5 @@ fun expectLong(param: String?): Long? { fun ApplicationCall.expectUrlParameter(name: String): String { return this.request.queryParameters[name] - ?: throw UtilError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI") + ?: throw EbicsProtocolError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI") } \ No newline at end of file diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt @@ -38,7 +38,6 @@ import java.io.* import java.security.PrivateKey import java.security.PublicKey import java.security.interfaces.RSAPrivateCrtKey -import javax.print.DocFlavor import javax.xml.XMLConstants import javax.xml.bind.JAXBContext import javax.xml.bind.JAXBElement @@ -411,7 +410,7 @@ class XMLUtil private constructor() { fun getNodeFromXpath(doc: Document, query: String): Node { val xpath = XPathFactory.newInstance().newXPath() val ret = xpath.evaluate(query, doc, XPathConstants.NODE) - ?: throw UtilError(HttpStatusCode.NotFound, "Unsuccessful XPath query string: $query") + ?: throw EbicsProtocolError(HttpStatusCode.NotFound, "Unsuccessful XPath query string: $query") return ret as Node } @@ -419,7 +418,7 @@ class XMLUtil private constructor() { val xpath = XPathFactory.newInstance().newXPath() val ret = xpath.evaluate(query, doc, XPathConstants.STRING) as String if (ret.isEmpty()) { - throw UtilError(HttpStatusCode.NotFound, "Unsuccessful XPath query string: $query") + throw EbicsProtocolError(HttpStatusCode.NotFound, "Unsuccessful XPath query string: $query") } return ret as String } diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt @@ -79,7 +79,7 @@ data class AmountWithCurrency( fun parseAmount(amount: String): AmountWithCurrency { val match = Regex("([A-Z]+):([0-9]+(\\.[0-9]+)?)").find(amount) ?: throw - UtilError(HttpStatusCode.BadRequest, "invalid amount: $amount") + EbicsProtocolError(HttpStatusCode.BadRequest, "invalid amount: $amount") val (currency, number) = match.destructured return AmountWithCurrency(currency, Amount(number)) } \ No newline at end of file