summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-05-19 18:05:33 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-05-19 18:05:33 +0530
commita43aa2e9e89281b27bbe823d0a9c3197a762c1d2 (patch)
treeb7d46cf427dc7d1f8ca6620543df9d0a2be49848
parent4dbd3ae0898c120666329b9b90edea7dc73e777d (diff)
downloadlibeufin-a43aa2e9e89281b27bbe823d0a9c3197a762c1d2.tar.gz
libeufin-a43aa2e9e89281b27bbe823d0a9c3197a762c1d2.tar.bz2
libeufin-a43aa2e9e89281b27bbe823d0a9c3197a762c1d2.zip
make tests pass
-rwxr-xr-xintegration-tests/test-ebics.py14
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt70
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt9
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt60
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt10
-rw-r--r--util/src/test/kotlin/EbicsMessagesTest.kt1
7 files changed, 88 insertions, 78 deletions
diff --git a/integration-tests/test-ebics.py b/integration-tests/test-ebics.py
index 50a217bf..f7a94bd4 100755
--- a/integration-tests/test-ebics.py
+++ b/integration-tests/test-ebics.py
@@ -5,7 +5,6 @@ from subprocess import call, Popen, PIPE
from time import sleep
import os
import socket
-import sqlite3
import hashlib
import base64
@@ -89,9 +88,11 @@ 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"], stdout=PIPE, stderr=PIPE)
+nexus = Popen(["./gradlew", "nexus:run", "--console=plain", "--args=serve"], stdout=PIPE, stderr=PIPE)
for i in range(10):
try:
get("http://localhost:5001/")
@@ -166,15 +167,6 @@ assertResponse(
#1.a, make a new nexus user.
-# "Create" the admin user first.
-dbconn = sqlite3.connect("nexus/libeufin-nexus.sqlite3")
-dbconn.execute(
- "INSERT INTO NexusUsers (id, password) VALUES (?, ?)",
- ("admin", sqlite3.Binary(hashlib.sha256(b"x").digest()))
-)
-dbconn.commit()
-dbconn.close()
-
assertResponse(
post(
"http://localhost:5001/users",
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index 9c7e225b..89d55e29 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -211,11 +211,13 @@ class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) {
object NexusUsersTable : IdTable<String>() {
override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
val passwordHash = text("password")
+ val superuser = bool("superuser")
}
class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable)
var passwordHash by NexusUsersTable.passwordHash
+ var superuser by NexusUsersTable.superuser
}
object BankAccountMapsTable : IntIdTable() {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index ac77efb8..6258679d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -7,18 +7,14 @@ import io.ktor.http.HttpStatusCode
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
-import tech.libeufin.util.Amount
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.EbicsClientSubscriberDetails
-import tech.libeufin.util.base64ToBytes
-import java.util.Random
+import tech.libeufin.util.*
import tech.libeufin.util.ebics_h004.EbicsTypes
import java.security.interfaces.RSAPublicKey
-import tech.libeufin.util.*
-import java.time.format.DateTimeFormatter
-import java.time.ZonedDateTime
import java.time.Instant
import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.util.*
fun isProduction(): Boolean {
return System.getenv("NEXUS_PRODUCTION") != null
@@ -75,8 +71,8 @@ fun getBankAccount(userId: String, accountId: String): BankAccountEntity {
val bankAccountMap = BankAccountMapEntity.find {
BankAccountMapsTable.nexusUser eq userId
}.firstOrNull() ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Bank account '$accountId' not found"
+ HttpStatusCode.NotFound,
+ "Bank account '$accountId' not found"
)
bankAccountMap.bankAccount
}
@@ -142,7 +138,7 @@ fun getEbicsTransport(userId: String, transportId: String? = null): EbicsSubscri
EbicsSubscribersTable.nexusUser eq userId
}.firstOrNull()
}
- return@transaction EbicsSubscriberEntity.find{
+ return@transaction EbicsSubscriberEntity.find {
EbicsSubscribersTable.id eq transportId and (EbicsSubscribersTable.nexusUser eq userId)
}.firstOrNull()
}
@@ -196,18 +192,23 @@ suspend fun downloadAndPersistC5xEbics(
RawBankTransactionEntity.new {
bankAccount = getBankAccountFromIban(
camt53doc.pickString(
- "//*[local-name()='Stmt']/*[local-name()='Acct']/*[local-name()='Id']/*[local-name()='IBAN']")
+ "//*[local-name()='Stmt']/*[local-name()='Acct']/*[local-name()='Id']/*[local-name()='IBAN']"
+ )
)
sourceFileName = fileName
- unstructuredRemittanceInformation = camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']")
+ unstructuredRemittanceInformation =
+ camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']")
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 = parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
+ bookingDate =
+ parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
nexusUser = extractNexusUser(userId)
- counterpartIban = camt53doc.pickString("//*[local-name()='${if (this.transactionType == "DBIT") "CdtrAcct" else "DbtrAcct"}']//*[local-name()='IBAN']")
- counterpartName = camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='${if (this.transactionType == "DBIT") "Cdtr" else "Dbtr"}']//*[local-name()='Nm']")
+ counterpartIban =
+ camt53doc.pickString("//*[local-name()='${if (this.transactionType == "DBIT") "CdtrAcct" else "DbtrAcct"}']//*[local-name()='IBAN']")
+ counterpartName =
+ camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='${if (this.transactionType == "DBIT") "Cdtr" else "Dbtr"}']//*[local-name()='Nm']")
counterpartBic = camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
}
}
@@ -217,7 +218,7 @@ suspend fun downloadAndPersistC5xEbics(
throw NexusError(
HttpStatusCode.BadGateway,
response.returnCode.errorCode
- )
+ )
}
}
}
@@ -426,7 +427,7 @@ fun extractNexusUser(param: String?): NexusUserEntity {
if (param == null) {
throw NexusError(HttpStatusCode.BadRequest, "Null Id given")
}
- return transaction{
+ return transaction {
NexusUserEntity.findById(param) ?: throw NexusError(
HttpStatusCode.NotFound,
"Subscriber: $param not found"
@@ -461,34 +462,23 @@ fun extractUserAndHashedPassword(authorizationHeader: String): Pair<String, Stri
* @param authorization the Authorization:-header line.
* @return user id
*/
-fun authenticateRequest(authorization: String?): String {
+fun authenticateRequest(authorization: String?): NexusUserEntity {
val headerLine = if (authorization == null) throw NexusError(
HttpStatusCode.BadRequest, "Authentication:-header line not found"
) else authorization
- val nexusUserId = transaction {
- val (username, password) = extractUserAndHashedPassword(headerLine)
- val user = NexusUserEntity.find {
- NexusUsersTable.id eq username
- }.firstOrNull()
- if (user == null) {
- throw NexusError(HttpStatusCode.Unauthorized, "Unknown user")
- }
- if (!CryptoUtil.checkpw(password, user.passwordHash)) {
- throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
- }
- return@transaction user.id.value
+ val (username, password) = extractUserAndHashedPassword(headerLine)
+ val user = NexusUserEntity.find {
+ NexusUsersTable.id eq username
+ }.firstOrNull()
+ if (user == null) {
+ throw NexusError(HttpStatusCode.Unauthorized, "Unknown user")
}
- return nexusUserId
+ if (!CryptoUtil.checkpw(password, user.passwordHash)) {
+ throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
+ }
+ return user
}
-fun authenticateAdminRequest(authorization: String?): String {
- val userId = authenticateRequest(authorization)
- if (!userId.equals("admin")) throw NexusError(
- HttpStatusCode.Forbidden,
- "Not the 'admin' user"
- )
- return userId
-}
/**
* Check if the subscriber has the right to use the (claimed) bank account.
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 7b764f2a..d48e283e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -190,6 +190,15 @@ data class User(
val password: String
)
+data class UserInfo(
+ val username: String,
+ val superuser: Boolean
+)
+
+data class UsersResponse(
+ val users: List<UserInfo>
+)
+
/** Response (list's element) type of "GET /bank-accounts" */
data class BankAccount(
var holder: String,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index ec2080e5..5bcf1008 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -20,6 +20,7 @@
package tech.libeufin.nexus
import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option
@@ -174,8 +175,8 @@ class Serve: CliktCommand("Run nexus HTTP server") {
}
class Superuser: CliktCommand("Add superuser or change pw") {
- val username by argument()
- val password by option().prompt(requireConfirmation = true, hideInput = true)
+ private val username by argument()
+ private val password by option().prompt(requireConfirmation = true, hideInput = true)
override fun run() {
dbCreateTables()
transaction {
@@ -184,8 +185,13 @@ class Superuser: CliktCommand("Add superuser or change pw") {
if (user == null) {
NexusUserEntity.new(username) {
this.passwordHash = hashedPw
+ this.superuser = true
}
} else {
+ if (!user.superuser) {
+ println("Can only change password for superuser with this command.")
+ throw ProgramResult(1)
+ }
user.passwordHash = hashedPw
}
}
@@ -267,30 +273,42 @@ fun serverMain() {
* Shows information about the requesting user.
*/
get("/user") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
val ret = transaction {
- NexusUserEntity.findById(userId)
+ val currentUser = authenticateRequest(call.request.headers["Authorization"])
UserResponse(
- username = userId,
- superuser = userId.equals("admin")
+ username = currentUser.id.value,
+ superuser = currentUser.superuser
)
}
call.respond(HttpStatusCode.OK, ret)
return@get
}
+
+ get("/users") {
+ val users = transaction {
+ transaction {
+ NexusUserEntity.all().map {
+ UserInfo(it.id.value, it.superuser)
+ }
+ }
+ }
+ val usersResp = UsersResponse(users)
+ call.respond(HttpStatusCode.OK, usersResp)
+ return@get
+ }
/**
- * Add a new ordinary user in the system (requires "admin" privileges)
+ * Add a new ordinary user in the system (requires superuser privileges)
*/
post("/users") {
- authenticateAdminRequest(call.request.headers["Authorization"])
val body = call.receive<User>()
- if (body.username.equals("admin")) throw NexusError(
- HttpStatusCode.Forbidden,
- "'admin' is a reserved username"
- )
transaction {
+ val currentUser = authenticateRequest(call.request.headers["Authorization"])
+ if (!currentUser.superuser) {
+ throw NexusError(HttpStatusCode.Forbidden, "only superuser can do that")
+ }
NexusUserEntity.new(body.username) {
passwordHash = hashpw(body.password)
+ superuser = false
}
}
call.respondText(
@@ -304,7 +322,7 @@ fun serverMain() {
* Shows the bank accounts belonging to the requesting user.
*/
get("/bank-accounts") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val bankAccounts = BankAccounts()
getBankAccountsFromNexusUserId(userId).forEach {
bankAccounts.accounts.add(
@@ -322,7 +340,7 @@ fun serverMain() {
* Submit one particular payment at the bank.
*/
post("/bank-accounts/prepared-payments/submit") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val body = call.receive<SubmitPayment>()
val preparedPayment = getPreparedPayment(body.uuid)
transaction {
@@ -368,7 +386,7 @@ fun serverMain() {
* Shows information about one particular prepared payment.
*/
get("/bank-accounts/{accountid}/prepared-payments/{uuid}") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val preparedPayment = getPreparedPayment(ensureNonNull(call.parameters["uuid"]))
if (preparedPayment.nexusUser.id.value != userId) throw NexusError(
HttpStatusCode.Forbidden,
@@ -393,7 +411,7 @@ fun serverMain() {
* Adds a new prepared payment.
*/
post("/bank-accounts/{accountid}/prepared-payments") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val bankAccount = getBankAccount(userId, ensureNonNull(call.parameters["accountid"]))
val body = call.receive<PreparedPaymentRequest>()
val amount = parseAmount(body.amount)
@@ -423,7 +441,7 @@ fun serverMain() {
* bank account details)
*/
post("/bank-accounts/collected-transactions") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val body = call.receive<CollectedTransaction>()
if (body.transport != null) {
when (body.transport.type) {
@@ -459,7 +477,7 @@ fun serverMain() {
* Asks list of transactions ALREADY downloaded from the bank.
*/
get("/bank-accounts/{accountid}/collected-transactions") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val bankAccount = expectNonNull(call.parameters["accountid"])
val start = call.request.queryParameters["start"]
val end = call.request.queryParameters["end"]
@@ -493,7 +511,7 @@ fun serverMain() {
* Adds a new bank transport.
*/
post("/bank-transports") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
// user exists and is authenticated.
val body = call.receive<JsonObject>()
val transport: Transport = getTransportFromJsonObject(body)
@@ -594,7 +612,7 @@ fun serverMain() {
* "transportName". Does not modify any DB table.
*/
post("/bank-transports/send{MSG}") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val body = call.receive<Transport>()
when (body.type) {
"ebics" -> {
@@ -619,7 +637,7 @@ fun serverMain() {
* "transportName". DOES alterate DB tables.
*/
post("/bank-transports/sync{MSG}") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val body = call.receive<Transport>()
when (body.type) {
"ebics" -> {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index f3dd6148..2b6b3ffc 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -220,7 +220,7 @@ class Taler(app: Route) {
return@get
}
app.post("/taler/transfer") {
- val exchangeId = authenticateRequest(call.request.headers["Authorization"])
+ val exchangeId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val transferRequest = call.receive<TalerTransferRequest>()
val amountObj = parseAmount(transferRequest.amount)
val creditorObj = parsePayto(transferRequest.credit_account)
@@ -304,7 +304,7 @@ class Taler(app: Route) {
}
/** Test-API that creates one new payment addressed to the exchange. */
app.post("/taler/admin/add-incoming") {
- val exchangeId = authenticateRequest(call.request.headers["Authorization"])
+ val exchangeId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val addIncomingData = call.receive<TalerAdminAddIncoming>()
val debtor = parsePayto(addIncomingData.debit_account)
val amount = parseAmount(addIncomingData.amount)
@@ -351,7 +351,7 @@ class Taler(app: Route) {
* places it into a further table. Eventually, another routine will perform
* all the prepared payments. */
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
- val userId = authenticateRequest(call.request.headers["Authorization"])
+ val userId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val nexusUser = getNexusUser(userId)
val callerBankAccount = expectNonNull(call.parameters["acctid"])
transaction {
@@ -477,7 +477,7 @@ class Taler(app: Route) {
*/
app.get("/taler/history/outgoing") {
/* sanitize URL arguments */
- val subscriberId = authenticateRequest(call.request.headers["Authorization"])
+ val subscriberId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val delta: Int = expectInt(call.expectUrlParameter("delta"))
val start: Long = handleStartArgument(call.request.queryParameters["start"], delta)
val startCmpOp = getComparisonOperator(delta, start, TalerRequestedPayments)
@@ -514,7 +514,7 @@ class Taler(app: Route) {
}
/** Responds only with the valid incoming payments */
app.get("/taler/history/incoming") {
- val exchangeId = authenticateRequest(call.request.headers["Authorization"])
+ val exchangeId = transaction { authenticateRequest(call.request.headers["Authorization"]).id.value }
val delta: Int = expectInt(call.expectUrlParameter("delta"))
val start: Long = handleStartArgument(call.request.queryParameters["start"], delta)
val history = TalerIncomingHistory()
diff --git a/util/src/test/kotlin/EbicsMessagesTest.kt b/util/src/test/kotlin/EbicsMessagesTest.kt
index e7f2cf1f..4c0032c8 100644
--- a/util/src/test/kotlin/EbicsMessagesTest.kt
+++ b/util/src/test/kotlin/EbicsMessagesTest.kt
@@ -10,7 +10,6 @@ import tech.libeufin.util.ebics_hev.SystemReturnCodeType
import tech.libeufin.util.ebics_s001.SignatureTypes
import tech.libeufin.util.CryptoUtil
import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h004.*
import javax.xml.datatype.DatatypeFactory
import kotlin.test.assertNotNull
import kotlin.test.assertTrue