libeufin

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

commit 7d33776d78129e3f4f3bbcfbcff0843fb4972bae
parent 51ec1715b78c510cd455dc13243b81e28f67c0ff
Author: Florian Dold <florian.dold@gmail.com>
Date:   Mon, 30 Sep 2019 10:01:04 +0200

make running the sandbox from the cmdline easier

Diffstat:
M.idea/gradle.xml | 1+
M.idea/misc.xml | 1+
MREADME | 28+++++++++-------------------
Mbuild.gradle | 16++++++++++++----
Mgradle/wrapper/gradle-wrapper.properties | 5+++--
Dsrc/main/kotlin/Main.kt | 204-------------------------------------------------------------------------------
Msrc/main/kotlin/tech/libeufin/DB.kt | 2+-
Asrc/main/kotlin/tech/libeufin/Main.kt | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 230 insertions(+), 230 deletions(-)

diff --git a/.idea/gradle.xml b/.idea/gradle.xml @@ -13,6 +13,7 @@ <option value="$PROJECT_DIR$" /> </set> </option> + <option name="useAutoImport" value="true" /> <option name="useQualifiedModuleNames" value="true" /> </GradleProjectSettings> </option> diff --git a/.idea/misc.xml b/.idea/misc.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="ExternalStorageConfigurationManager" enabled="true" /> + <component name="ProjectRootManager" version="2" project-jdk-name="12" project-jdk-type="JavaSDK" /> </project> \ No newline at end of file diff --git a/README b/README @@ -1,5 +1,5 @@ -Description. -============ +Description +=========== The Libeufin Sandbox aims at implementing the server side of multiple banking protocols currently used in the European Union. Notably, the @@ -7,25 +7,15 @@ EBICS, FinTS, and the major protocols that banks will employ to respect the PSD2 regulation: https://ec.europa.eu/info/law/payment-services-psd-2-directive-eu-2015-2366_en -Building and running the sandbox. -================================= +Running the sandbox +=================== -The sandox must be exported as JAR in order to be run. -The following command exports the JAR in GNU/Linux systems. +Run the sandbox with the following command $ cd <this repository> -$ ./gradlew jar +$ ./gradlew run --console=plain -If the previous step worked, then a new JAR file should -be located under <this repository>/build/libs/sandbox-$VERSION.jar, -where $VERSION reflects the value of the 'version' global -variable from <this repository>/build.gradle. +Documentation +============= -The following command runs the sandbox: - -$ java -jar <this repository>/build/libs/sandbox-$VERSION.jar - -MS Windows users. -================= -The steps to follow are the same as in GNU/Linux systems, except -that the command "gradlew.bat jar" would produce the JAR file. +See https://docs.libeufin.tech/ for the documentation. diff --git a/build.gradle b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '1.3.41' + id 'application' + id 'org.jetbrains.kotlin.jvm' version '1.3.50' } version '1.0-SNAPSHOT' @@ -28,14 +29,21 @@ dependencies { } compileKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions { + jvmTarget = "1.8" + } } compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions { + jvmTarget = "1.8" + } } -jar { +application { + mainClassName = "tech.libeufin.MainKt" +} +jar { manifest { attributes "Main-Class": "tech.libeufin.MainKt" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Mon Sep 30 09:20:29 CEST 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt @@ -1,203 +0,0 @@ -/* - * This file is part of LibEuFin. - * Copyright (C) 2019 Stanisci and Dold. - - * LibEuFin is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3, or - * (at your option) any later version. - - * LibEuFin is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General - * Public License for more details. - - * You should have received a copy of the GNU Affero General Public - * License along with LibEuFin; see the file COPYING. If not, see - * <http://www.gnu.org/licenses/> - */ - -package tech.libeufin - -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.CallLogging -import io.ktor.features.ContentNegotiation -import io.ktor.gson.gson -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.request.receive -import io.ktor.request.receiveText -import io.ktor.response.respond -import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.post -import io.ktor.routing.routing -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import org.jetbrains.exposed.sql.transactions.transaction -import org.w3c.dom.Document -import tech.libeufin.messages.HEVResponseDataType -import tech.libeufin.tech.libeufin.* -import java.text.DateFormat -import javax.xml.bind.JAXBElement - -fun main() { - - val xmlProcess = XMLTransform() - val logger = getLogger() - dbCreateTables() - - val server = embeddedServer(Netty, port = 5000) { - - install(CallLogging) - install(ContentNegotiation) { - gson { - setDateFormat(DateFormat.LONG) - setPrettyPrinting() - } - } - routing { - get("/") { - logger.debug("GET: not implemented") - call.respondText("Hello LibEuFin!", ContentType.Text.Plain) - return@get - } - - post("/admin/customers") { - val body = try { - call.receive<CustomerRequest>() - } catch (e: Exception) { - e.printStackTrace() - call.respond( - HttpStatusCode.BadRequest, - SandboxError(e.message.toString()) - ) - return@post - } - logger.info(body.toString()) - - val returnId = transaction { - val myUserId = EbicsUser.new { } - val myPartnerId = EbicsPartner.new { } - val mySystemId = EbicsSystem.new { } - val subscriber = EbicsSubscriber.new { - userId = myUserId - partnerId = myPartnerId - systemId = mySystemId - state = SubscriberStates.NEW - } - println("subscriber ID: ${subscriber.id.value}") - val customer = BankCustomer.new { - name = body.name - ebicsSubscriber = subscriber - } - println("name: ${customer.name}") - return@transaction customer.id.value - } - - call.respond( - HttpStatusCode.OK, - CustomerResponse(id = returnId) - ) - - return@post - } - - get("/admin/customers/{id}") { - - val id: Int = try { - call.parameters["id"]!!.toInt() - } catch (e: NumberFormatException) { - call.respond( - HttpStatusCode.BadRequest, - SandboxError(e.message.toString()) - ) - return@get - } - - logger.info("Querying ID: $id") - - val customerInfo = transaction { - val customer = BankCustomer.findById(id) ?: return@transaction null - CustomerInfo( - customer.name, - customerEbicsInfo = CustomerEbicsInfo( - customer.ebicsSubscriber.userId.id.value - ) - ) - } - - if (null == customerInfo) { - call.respond( - HttpStatusCode.NotFound, - SandboxError("id $id not found") - ) - return@get - } - - call.respond(HttpStatusCode.OK, customerInfo) - } - - post("/ebicsweb") { - val body: String = call.receiveText() - logger.debug("Body: $body") - - val isValid = xmlProcess.validateFromString(body) - - if (!isValid) { - logger.error("Invalid request received") - call.respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.BadRequest - ) { "Bad request" } - return@post - } - - val bodyDocument: Document? = xmlProcess.parseStringIntoDom(body) - if (null == bodyDocument) { - /* Should never happen. */ - logger.error("A valid document failed to parse into DOM!") - call.respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.InternalServerError - ) { "Internal server error" } - return@post - } - logger.info(bodyDocument.documentElement.localName) - - when (bodyDocument.documentElement.localName) { - "ebicsHEVRequest" -> { - val hevResponse = HEVResponse( - "000000", - "EBICS_OK", - arrayOf( - ProtocolAndVersion("H003", "02.40"), - ProtocolAndVersion("H004", "02.50") - ) - ) - - val jaxbHEV: JAXBElement<HEVResponseDataType> = hevResponse.makeHEVResponse() - val responseText: String? = xmlProcess.getStringFromJaxb(jaxbHEV) - // FIXME: check if String is actually non-NULL! - call.respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.OK - ) { responseText.toString() } - return@post - } - else -> { - /* Log to console and return "unknown type" */ - logger.info("Unknown message, just logging it!") - call.respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.NotFound - ) { "Not found" } - return@post - } - } - } - } - } - server.start(wait = true) -} -\ No newline at end of file diff --git a/src/main/kotlin/tech/libeufin/DB.kt b/src/main/kotlin/tech/libeufin/DB.kt @@ -1,4 +1,4 @@ -package tech.libeufin.tech.libeufin +package tech.libeufin import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.sql.* diff --git a/src/main/kotlin/tech/libeufin/Main.kt b/src/main/kotlin/tech/libeufin/Main.kt @@ -0,0 +1,202 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2019 Stanisci and Dold. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin + +import io.ktor.application.call +import io.ktor.application.install +import io.ktor.features.CallLogging +import io.ktor.features.ContentNegotiation +import io.ktor.gson.gson +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.request.receive +import io.ktor.request.receiveText +import io.ktor.response.respond +import io.ktor.response.respondText +import io.ktor.routing.get +import io.ktor.routing.post +import io.ktor.routing.routing +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import org.jetbrains.exposed.sql.transactions.transaction +import org.w3c.dom.Document +import tech.libeufin.messages.HEVResponseDataType +import java.text.DateFormat +import javax.xml.bind.JAXBElement + +fun main() { + + val xmlProcess = XMLTransform() + val logger = getLogger() + dbCreateTables() + + val server = embeddedServer(Netty, port = 5000) { + + install(CallLogging) + install(ContentNegotiation) { + gson { + setDateFormat(DateFormat.LONG) + setPrettyPrinting() + } + } + routing { + get("/") { + logger.debug("GET: not implemented") + call.respondText("Hello LibEuFin!", ContentType.Text.Plain) + return@get + } + + post("/admin/customers") { + val body = try { + call.receive<CustomerRequest>() + } catch (e: Exception) { + e.printStackTrace() + call.respond( + HttpStatusCode.BadRequest, + SandboxError(e.message.toString()) + ) + return@post + } + logger.info(body.toString()) + + val returnId = transaction { + val myUserId = EbicsUser.new { } + val myPartnerId = EbicsPartner.new { } + val mySystemId = EbicsSystem.new { } + val subscriber = EbicsSubscriber.new { + userId = myUserId + partnerId = myPartnerId + systemId = mySystemId + state = SubscriberStates.NEW + } + println("subscriber ID: ${subscriber.id.value}") + val customer = BankCustomer.new { + name = body.name + ebicsSubscriber = subscriber + } + println("name: ${customer.name}") + return@transaction customer.id.value + } + + call.respond( + HttpStatusCode.OK, + CustomerResponse(id = returnId) + ) + + return@post + } + + get("/admin/customers/{id}") { + + val id: Int = try { + call.parameters["id"]!!.toInt() + } catch (e: NumberFormatException) { + call.respond( + HttpStatusCode.BadRequest, + SandboxError(e.message.toString()) + ) + return@get + } + + logger.info("Querying ID: $id") + + val customerInfo = transaction { + val customer = BankCustomer.findById(id) ?: return@transaction null + CustomerInfo( + customer.name, + customerEbicsInfo = CustomerEbicsInfo( + customer.ebicsSubscriber.userId.id.value + ) + ) + } + + if (null == customerInfo) { + call.respond( + HttpStatusCode.NotFound, + SandboxError("id $id not found") + ) + return@get + } + + call.respond(HttpStatusCode.OK, customerInfo) + } + + post("/ebicsweb") { + val body: String = call.receiveText() + logger.debug("Body: $body") + + val isValid = xmlProcess.validateFromString(body) + + if (!isValid) { + logger.error("Invalid request received") + call.respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.BadRequest + ) { "Bad request" } + return@post + } + + val bodyDocument: Document? = xmlProcess.parseStringIntoDom(body) + if (null == bodyDocument) { + /* Should never happen. */ + logger.error("A valid document failed to parse into DOM!") + call.respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.InternalServerError + ) { "Internal server error" } + return@post + } + logger.info(bodyDocument.documentElement.localName) + + when (bodyDocument.documentElement.localName) { + "ebicsHEVRequest" -> { + val hevResponse = HEVResponse( + "000000", + "EBICS_OK", + arrayOf( + ProtocolAndVersion("H003", "02.40"), + ProtocolAndVersion("H004", "02.50") + ) + ) + + val jaxbHEV: JAXBElement<HEVResponseDataType> = hevResponse.makeHEVResponse() + val responseText: String? = xmlProcess.getStringFromJaxb(jaxbHEV) + // FIXME: check if String is actually non-NULL! + call.respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.OK + ) { responseText.toString() } + return@post + } + else -> { + /* Log to console and return "unknown type" */ + logger.info("Unknown message, just logging it!") + call.respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.NotFound + ) { "Not found" } + return@post + } + } + } + } + } + server.start(wait = true) +} +\ No newline at end of file