summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-01-21 12:34:41 +0100
committerFlorian Dold <florian@dold.me>2021-01-21 12:34:41 +0100
commit24e078e9db5f57802d7b2ce94d74def0f930baca (patch)
tree49e14f03b1960ad271f3196deee13bfb6803839a
parent995605215400f83d56a7bf9bae29590a7e461416 (diff)
downloadlibeufin-24e078e9db5f57802d7b2ce94d74def0f930baca.tar.gz
libeufin-24e078e9db5f57802d7b2ce94d74def0f930baca.tar.bz2
libeufin-24e078e9db5f57802d7b2ce94d74def0f930baca.zip
restrict resource names, cleanup
-rw-r--r--.idea/dictionaries/dold.xml1
-rwxr-xr-xcli/bin/libeufin-cli30
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt50
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt4
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt28
-rw-r--r--sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt16
-rw-r--r--util/src/test/kotlin/XmlUtilTest.kt2
8 files changed, 83 insertions, 50 deletions
diff --git a/.idea/dictionaries/dold.xml b/.idea/dictionaries/dold.xml
index 12a9811b..48c7f058 100644
--- a/.idea/dictionaries/dold.xml
+++ b/.idea/dictionaries/dold.xml
@@ -11,6 +11,7 @@
<w>gnunet</w>
<w>iban</w>
<w>infos</w>
+ <w>keyletter</w>
<w>libeufin</w>
<w>payto</w>
<w>pdng</w>
diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli
index 5e89e1e8..c7aabe54 100755
--- a/cli/bin/libeufin-cli
+++ b/cli/bin/libeufin-cli
@@ -359,7 +359,7 @@ def restore_backup(obj, backup_file, passphrase, connection_name):
tell_user(resp)
-@connections.command(help="make new Ebics bank connection")
+@connections.command(help="make new EBICS bank connection")
@click.option("--ebics-url", help="EBICS URL", required=True)
@click.option("--host-id", help="Host ID", required=True)
@click.option("--partner-id", help="Partner ID", required=True)
@@ -390,10 +390,10 @@ def new_ebics_connection(
tell_user(resp)
-@connections.command(help="synchronize the bank connection")
+@connections.command(help="Initialize the bank connection.")
@click.argument("connection-name")
@click.pass_obj
-def sync(obj, connection_name):
+def connect(obj, connection_name):
url = urljoin(obj.nexus_base_url, f"/bank-connections/{connection_name}/connect")
try:
resp = post(
@@ -405,7 +405,7 @@ def sync(obj, connection_name):
tell_user(resp)
-@connections.command(help="import one bank account, chosen from the downloaded ones")
+@connections.command(help="Import one bank account, chosen from the downloaded ones.")
@click.option(
"--offered-account-id", help="Name of the account to import", required=True
)
@@ -439,7 +439,7 @@ def import_bank_account(
tell_user(resp)
-@connections.command(help="download bank accounts in raw format WITHOUT importing them")
+@connections.command(help="Update list of bank accounts available through this connection.")
@click.argument("connection-name")
@click.pass_obj
def download_bank_accounts(obj, connection_name):
@@ -458,10 +458,26 @@ def download_bank_accounts(obj, connection_name):
tell_user(resp)
-@connections.command(help="list the connections")
+@connections.command(help="List the connections.")
@click.pass_obj
def list_connections(obj):
- url = urljoin(obj.nexus_base_url, "/bank-connections/")
+ url = urljoin(obj.nexus_base_url, "/bank-connections")
+ try:
+ resp = get(
+ url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
+ )
+ except Exception:
+ print("Could not reach nexus at " + url)
+ exit(1)
+
+ tell_user(resp, withsuccess=True)
+
+
+@connections.command(help="Show the status of a bank connection.")
+@click.argument("connection-name")
+@click.pass_obj
+def show_connection(obj, connection_name):
+ url = urljoin(obj.nexus_base_url, f"/bank-connections/{connection_name}")
try:
resp = get(
url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index 6afeb711..2f2791b8 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -592,7 +592,7 @@ fun getEbicsConnectionDetails(conn: NexusBankConnectionEntity): Any {
details.put("userId", ebicsSubscriber.userId)
val node = mapper.createObjectNode()
node.put("type", conn.type)
- node.put("owner", conn.owner.id.value)
+ node.put("owner", conn.owner.username)
node.set<JsonNode>("details", details)
return node
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index 79fb3340..7d4e368e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -121,6 +121,22 @@ fun ApplicationCall.expectUrlParameter(name: String): String {
?: throw NexusError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI")
}
+fun isValidResourceName(name: String): Boolean {
+ return name.matches(Regex("[a-z]([-a-z0-9]*[a-z0-9])?"))
+}
+
+fun requireValidResourceName(name: String): String {
+ if (!isValidResourceName(name)) {
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Invalid resource name. The first character must be a lowercase letter, " +
+ "and all following characters (except for the last character) must be a dash, " +
+ "lowercase letter, or digit. The last character must be a lowercase letter or digit."
+ )
+ }
+ return name
+}
+
suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T {
try {
return this.receive<T>()
@@ -326,10 +342,11 @@ fun serverMain(dbName: String, host: String, port: Int) {
// Add a new ordinary user in the system (requires superuser privileges)
post("/users") {
val body = call.receiveJson<CreateUserRequest>()
+ val requestedUsername = requireValidResourceName(body.username)
transaction {
requireSuperuser(call.request)
NexusUserEntity.new {
- username = body.username
+ username = requestedUsername
passwordHash = CryptoUtil.hashpw(body.password)
superuser = false
}
@@ -448,7 +465,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
resourceType = "bank-account"
resourceId = accountId
this.taskCronspec = schedSpec.cronspec
- this.taskName = schedSpec.name
+ this.taskName = requireValidResourceName(schedSpec.name)
this.taskType = schedSpec.type
this.taskParams =
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(schedSpec.params)
@@ -527,8 +544,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
post("/bank-accounts/{accountid}/payment-initiations/{uuid}/submit") {
requireSuperuser(call.request)
val uuid = ensureLong(call.parameters["uuid"])
- val accountId = ensureNonNull(call.parameters["accountid"])
- val res = transaction {
+ transaction {
authenticateRequest(call.request)
}
submitPaymentInitiation(client, uuid)
@@ -539,7 +555,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
post("/bank-accounts/{accountid}/submit-all-payment-initiations") {
requireSuperuser(call.request)
val accountId = ensureNonNull(call.parameters["accountid"])
- val res = transaction {
+ transaction {
authenticateRequest(call.request)
}
submitAllPaymentInitiations(client, accountId)
@@ -698,6 +714,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
requireSuperuser(call.request)
// user exists and is authenticated.
val body = call.receive<CreateBankConnectionRequestJson>()
+ requireValidResourceName(body.name)
transaction {
val user = authenticateRequest(call.request)
val existingConn =
@@ -774,11 +791,11 @@ fun serverMain(dbName: String, host: String, port: Int) {
call.respond(connList)
}
- get("/bank-connections/{connectionId}") {
+ get("/bank-connections/{connectionName}") {
requireSuperuser(call.request)
val resp = transaction {
val user = authenticateRequest(call.request)
- val conn = requireBankConnection(call, "connectionId")
+ val conn = requireBankConnection(call, "connectionName")
when (conn.type) {
"ebics" -> {
getEbicsConnectionDetails(conn)
@@ -794,12 +811,12 @@ fun serverMain(dbName: String, host: String, port: Int) {
call.respond(resp)
}
- post("/bank-connections/{connid}/export-backup") {
+ post("/bank-connections/{connectionName}/export-backup") {
requireSuperuser(call.request)
transaction { authenticateRequest(call.request) }
val body = call.receive<BackupRequestJson>()
val response = run {
- val conn = requireBankConnection(call, "connid")
+ val conn = requireBankConnection(call, "connectionName")
when (conn.type) {
"ebics" -> {
exportEbicsKeyBackup(conn.connectionId, body.passphrase)
@@ -819,11 +836,11 @@ fun serverMain(dbName: String, host: String, port: Int) {
)
}
- post("/bank-connections/{connid}/connect") {
+ post("/bank-connections/{connectionName}/connect") {
requireSuperuser(call.request)
val conn = transaction {
authenticateRequest(call.request)
- requireBankConnection(call, "connid")
+ requireBankConnection(call, "connectionName")
}
when (conn.type) {
"ebics" -> {
@@ -833,11 +850,11 @@ fun serverMain(dbName: String, host: String, port: Int) {
call.respond(object {})
}
- get("/bank-connections/{connid}/keyletter") {
+ get("/bank-connections/{connectionName}/keyletter") {
requireSuperuser(call.request)
val conn = transaction {
authenticateRequest(call.request)
- requireBankConnection(call, "connid")
+ requireBankConnection(call, "connectionName")
}
when (conn.type) {
"ebics" -> {
@@ -848,11 +865,11 @@ fun serverMain(dbName: String, host: String, port: Int) {
}
}
- get("/bank-connections/{connid}/messages") {
+ get("/bank-connections/{connectionName}/messages") {
requireSuperuser(call.request)
val ret = transaction {
val list = BankMessageList()
- val conn = requireBankConnection(call, "connid")
+ val conn = requireBankConnection(call, "connectionName")
NexusBankMessageEntity.find { NexusBankMessagesTable.bankConnection eq conn.id }.map {
list.bankMessages.add(
BankMessageInfo(
@@ -928,6 +945,7 @@ fun serverMain(dbName: String, host: String, port: Int) {
post("/facades") {
requireSuperuser(call.request)
val body = call.receive<FacadeInfo>()
+ requireValidResourceName(body.name)
if (body.type != "taler-wire-gateway") throw NexusError(
HttpStatusCode.NotImplemented,
"Facade type '${body.type}' is not implemented"
@@ -1025,6 +1043,6 @@ fun serverMain(dbName: String, host: String, port: Int) {
}
}
}
- logger.info("Up and running")
+ logger.info("LibEuFin Nexus running on port $port")
server.start(wait = true)
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index d0b024b1..c6add963 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -313,7 +313,7 @@ object BankAccountReportsTable : IntIdTable() {
}
fun dbDropTables(dbConnectionString: String) {
- Database.connect("${dbConnectionString}")
+ Database.connect(dbConnectionString)
transaction {
SchemaUtils.drop(
EbicsSubscribersTable,
@@ -331,7 +331,7 @@ fun dbDropTables(dbConnectionString: String) {
}
fun dbCreateTables(dbConnectionString: String) {
- Database.connect("${dbConnectionString}")
+ Database.connect(dbConnectionString)
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
transaction {
SchemaUtils.create(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 0e268717..e06fd26b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -145,7 +145,7 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement(
}
}
val text = XMLUtil.convertJaxbToString(responseXml)
- LOGGER.info("responding with:\n${text}")
+ logger.info("responding with:\n${text}")
if (!XMLUtil.validateFromString(text)) throw SandboxError(
HttpStatusCode.InternalServerError,
"Outgoint EBICS key management response is invalid"
@@ -548,8 +548,8 @@ private fun parsePain001(paymentRequest: String): PainParseResult {
* Process a payment request in the pain.001 format.
*/
private fun handleCct(paymentRequest: String, initiatorName: String) {
- LOGGER.debug("Handling CCT")
- LOGGER.debug("Pain.001: $paymentRequest")
+ logger.debug("Handling CCT")
+ logger.debug("Pain.001: $paymentRequest")
val parseResult = parsePain001(paymentRequest)
transaction {
try {
@@ -580,7 +580,7 @@ private fun handleCct(paymentRequest: String, initiatorName: String) {
}
private fun handleEbicsC53(requestContext: RequestContext): ByteArray {
- LOGGER.debug("Handling C53 request")
+ logger.debug("Handling C53 request")
val camt = constructCamtResponse(
53,
requestContext.requestObject.header,
@@ -608,7 +608,7 @@ private suspend fun ApplicationCall.handleEbicsHia(header: EbicsUnsecuredRequest
val ok = transaction {
val ebicsSubscriber = findEbicsSubscriber(header.static.partnerID, header.static.userID, header.static.systemID)
if (ebicsSubscriber == null) {
- LOGGER.warn("ebics subscriber not found")
+ logger.warn("ebics subscriber not found")
throw EbicsInvalidRequestError()
}
when (ebicsSubscriber.state) {
@@ -655,7 +655,7 @@ private suspend fun ApplicationCall.handleEbicsIni(header: EbicsUnsecuredRequest
val ebicsSubscriber =
findEbicsSubscriber(header.static.partnerID, header.static.userID, header.static.systemID)
if (ebicsSubscriber == null) {
- LOGGER.warn("ebics subscriber ('${header.static.partnerID}' / '${header.static.userID}' / '${header.static.systemID}') not found")
+ logger.warn("ebics subscriber ('${header.static.partnerID}' / '${header.static.userID}' / '${header.static.systemID}') not found")
throw EbicsInvalidRequestError()
}
when (ebicsSubscriber.state) {
@@ -676,7 +676,7 @@ private suspend fun ApplicationCall.handleEbicsIni(header: EbicsUnsecuredRequest
}
return@transaction true
}
- LOGGER.info("Signature key inserted in database _and_ subscriber state changed accordingly")
+ logger.info("Signature key inserted in database _and_ subscriber state changed accordingly")
if (ok) {
respondEbicsKeyManagement("[EBICS_OK]", "000000", "000000")
} else {
@@ -709,7 +709,7 @@ private suspend fun ApplicationCall.handleEbicsHpb(
}
val validationResult =
XMLUtil.verifyEbicsDocument(requestDocument, subscriberKeys.authenticationPublicKey)
- LOGGER.info("validationResult: $validationResult")
+ logger.info("validationResult: $validationResult")
if (!validationResult) {
throw EbicsKeyManagementError("invalid signature", "90000")
}
@@ -750,7 +750,7 @@ private fun ApplicationCall.ensureEbicsHost(requestHostID: String): EbicsHostPub
val ebicsHost =
EbicsHostEntity.find { EbicsHostsTable.hostID.upperCase() eq requestHostID.toUpperCase() }.firstOrNull()
if (ebicsHost == null) {
- LOGGER.warn("client requested unknown HostID ${requestHostID}")
+ logger.warn("client requested unknown HostID ${requestHostID}")
throw EbicsKeyManagementError("[EBICS_INVALID_HOST_ID]", "091011")
}
val encryptionPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.bytes)
@@ -765,7 +765,7 @@ private fun ApplicationCall.ensureEbicsHost(requestHostID: String): EbicsHostPub
private suspend fun ApplicationCall.receiveEbicsXml(): Document {
val body: String = receiveText()
- LOGGER.debug("Data received: $body")
+ logger.debug("Data received: $body")
val requestDocument: Document? = XMLUtil.parseStringIntoDom(body)
if (requestDocument == null || (!XMLUtil.validateFromDom(requestDocument))) {
println("Problematic document was: $requestDocument")
@@ -1136,11 +1136,11 @@ private fun makeRequestContext(requestObject: EbicsRequest): RequestContext {
suspend fun ApplicationCall.ebicsweb() {
val requestDocument = receiveEbicsXml()
- LOGGER.info("Processing ${requestDocument.documentElement.localName}")
+ logger.info("Processing ${requestDocument.documentElement.localName}")
when (requestDocument.documentElement.localName) {
"ebicsUnsecuredRequest" -> {
val requestObject = requestDocument.toObject<EbicsUnsecuredRequest>()
- LOGGER.info("Serving a ${requestObject.header.static.orderDetails.orderType} request")
+ logger.info("Serving a ${requestObject.header.static.orderDetails.orderType} request")
val orderData = requestObject.body.dataTransfer.orderData.value
val header = requestObject.header
@@ -1161,7 +1161,7 @@ suspend fun ApplicationCall.ebicsweb() {
}
val strResp = XMLUtil.convertJaxbToString(hevResponse)
- LOGGER.debug("HEV response: $strResp")
+ logger.debug("HEV response: $strResp")
if (!XMLUtil.validateFromString(strResp)) throw SandboxError(
HttpStatusCode.InternalServerError,
"Outgoing HEV response is invalid"
@@ -1225,7 +1225,7 @@ suspend fun ApplicationCall.ebicsweb() {
}
else -> {
/* Log to console and return "unknown type" */
- LOGGER.info("Unknown message, just logging it!")
+ logger.info("Unknown message, just logging it!")
respond(
HttpStatusCode.NotImplemented,
SandboxError(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 87ad4746..0653494b 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -60,7 +60,6 @@ import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.output.CliktHelpFormatter
import com.github.ajalt.clikt.parameters.options.default
-import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.versionOption
import com.github.ajalt.clikt.parameters.types.int
@@ -83,7 +82,6 @@ import tech.libeufin.util.ebics_h004.EbicsResponse
import tech.libeufin.util.ebics_h004.EbicsTypes
import java.util.*
import kotlin.random.Random
-import kotlin.system.exitProcess
const val DEFAULT_DB_CONNECTION = "jdbc:sqlite:/tmp/libeufin-sandbox.sqlite3"
@@ -93,7 +91,7 @@ class UnacceptableFractional(badNumber: BigDecimal) : Exception(
"Unacceptable fractional part ${badNumber}"
)
-lateinit var LOGGER: Logger
+private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
data class SandboxError(val statusCode: HttpStatusCode, val reason: String) : Exception()
data class SandboxErrorJson(val error: SandboxErrorDetailJson)
@@ -126,7 +124,6 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
private val logLevel by option()
private val port by option().int().default(5000)
override fun run() {
- LOGGER = LoggerFactory.getLogger("tech.libeufin.sandbox")
setLogLevel(logLevel)
serverMain(dbConnString, port)
}
@@ -207,10 +204,11 @@ fun main(args: Array<String>) {
fun serverMain(dbName: String, port: Int) {
execThrowableOrTerminate { dbCreateTables(dbName) }
+ val myLogger = logger
val server = embeddedServer(Netty, port = port) {
install(CallLogging) {
this.level = Level.DEBUG
- this.logger = LOGGER
+ this.logger = myLogger
}
install(ContentNegotiation) {
jackson {
@@ -225,7 +223,7 @@ fun serverMain(dbName: String, port: Int) {
}
install(StatusPages) {
exception<ArithmeticException> { cause ->
- LOGGER.error("Exception while handling '${call.request.uri}'", cause)
+ logger.error("Exception while handling '${call.request.uri}'", cause)
call.respondText(
"Invalid arithmetic attempted.",
ContentType.Text.Plain,
@@ -261,7 +259,7 @@ fun serverMain(dbName: String, port: Int) {
)
}
exception<SandboxError> { cause ->
- LOGGER.error("Exception while handling '${call.request.uri}'", cause)
+ logger.error("Exception while handling '${call.request.uri}'", cause)
call.respond(
cause.statusCode,
SandboxErrorJson(
@@ -273,7 +271,7 @@ fun serverMain(dbName: String, port: Int) {
)
}
exception<Throwable> { cause ->
- LOGGER.error("Exception while handling '${call.request.uri}'", cause)
+ logger.error("Exception while handling '${call.request.uri}'", cause)
call.respondText("Internal server error.", ContentType.Text.Plain, HttpStatusCode.InternalServerError)
}
}
@@ -566,6 +564,6 @@ fun serverMain(dbName: String, port: Int) {
}
}
}
- LOGGER.info("Up and running")
+ logger.info("LibEuFin Nexus running on port $port")
server.start(wait = true)
} \ No newline at end of file
diff --git a/util/src/test/kotlin/XmlUtilTest.kt b/util/src/test/kotlin/XmlUtilTest.kt
index 57394b8b..32b91a23 100644
--- a/util/src/test/kotlin/XmlUtilTest.kt
+++ b/util/src/test/kotlin/XmlUtilTest.kt
@@ -14,7 +14,7 @@ import java.util.*
import javax.xml.transform.stream.StreamSource
import tech.libeufin.util.XMLUtil.Companion.signEbicsResponse
-val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util")
+private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.XmlUnitTest")
class XmlUtilTest {