[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: fix timestamp format, normalize intern
From: |
gnunet |
Subject: |
[libeufin] branch master updated: fix timestamp format, normalize internal IBANs |
Date: |
Sun, 24 Sep 2023 21:21:15 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new 1a641ba7 fix timestamp format, normalize internal IBANs
1a641ba7 is described below
commit 1a641ba73be83f379a2e0c9f876b3c6ff30d3ed7
Author: Florian Dold <florian@dold.me>
AuthorDate: Sun Sep 24 21:18:27 2023 +0200
fix timestamp format, normalize internal IBANs
---
.../main/kotlin/tech/libeufin/bank/BankMessages.kt | 21 ++--
.../tech/libeufin/bank/CorebankApiHandlers.kt | 133 +++++++++------------
.../src/main/kotlin/tech/libeufin/bank/Database.kt | 1 -
.../tech/libeufin/bank/IntegrationApiHandlers.kt | 48 +++-----
bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 7 +-
.../tech/libeufin/bank/WireGatewayApiHandlers.kt | 41 ++++---
bank/src/test/kotlin/LibeuFinApiTest.kt | 40 +++++++
util/src/main/kotlin/{Payto.kt => IbanPayto.kt} | 22 +++-
util/src/test/kotlin/PaytoTest.kt | 4 +-
9 files changed, 178 insertions(+), 139 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index 429a89ef..86d52ccd 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -24,6 +24,7 @@ import io.ktor.server.application.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.*
+import kotlin.reflect.jvm.internal.impl.types.AbstractStubType
/**
* Allowed lengths for fractional digits in amounts.
@@ -38,9 +39,15 @@ enum class FracDigits(howMany: Int) {
* Timestamp containing the number of seconds since epoch.
*/
@Serializable
-data class Timestamp(
- val t_s: Long // FIXME (?): not supporting "never" at the moment.
-)
+data class TalerProtocolTimestamp(
+ val t_s: Long, // FIXME (?): not supporting "never" at the moment.
+) {
+ companion object {
+ fun fromMicroseconds(uSec: Long): TalerProtocolTimestamp {
+ return TalerProtocolTimestamp(uSec / 1000000)
+ }
+ }
+}
/**
* HTTP response type of successful token refresh.
@@ -51,7 +58,7 @@ data class Timestamp(
@Serializable
data class TokenSuccessResponse(
val access_token: String,
- val expiration: Timestamp
+ val expiration: TalerProtocolTimestamp
)
/**
@@ -545,7 +552,7 @@ data class AddIncomingRequest(
*/
@Serializable
data class AddIncomingResponse(
- val timestamp: Long,
+ val timestamp: TalerProtocolTimestamp,
val row_id: Long
)
@@ -572,7 +579,7 @@ data class IncomingHistory(
data class IncomingReserveTransaction(
val type: String = "RESERVE",
val row_id: Long, // DB row ID of the payment.
- val date: Long, // microseconds timestamp.
+ val date: TalerProtocolTimestamp,
val amount: String,
val debit_account: String, // Payto of the sender.
val reserve_pub: String
@@ -596,6 +603,6 @@ data class TransferRequest(
*/
@Serializable
data class TransferResponse(
- val timestamp: Long,
+ val timestamp: TalerProtocolTimestamp,
val row_id: Long
)
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
index e1939073..eaaa33ad 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -26,7 +26,8 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
}
post("/accounts/{USERNAME}/token") {
- val customer = call.authenticateBankRequest(db,
TokenScope.refreshable) ?: throw unauthorized("Authentication failed")
+ val customer =
+ call.authenticateBankRequest(db, TokenScope.refreshable) ?: throw
unauthorized("Authentication failed")
val endpointOwner = call.maybeUriComponent("USERNAME")
if (customer.login != endpointOwner) throw forbidden(
"User has no rights on this enpoint", TalerErrorCode.TALER_EC_END
// FIXME: need generic forbidden
@@ -51,8 +52,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
}
val maxDurationTime: Long = ctx.maxAuthTokenDurationUs
if (req.duration != null && req.duration.d_us > maxDurationTime) throw
forbidden(
- "Token duration bigger than bank's limit",
- // FIXME: define new EC for this case.
+ "Token duration bigger than bank's limit", // FIXME: define new EC
for this case.
TalerErrorCode.TALER_EC_END
)
val tokenDurationUs = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION_US
@@ -61,8 +61,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
)
val expirationTimestampUs: Long = getNowUs() + tokenDurationUs
if (expirationTimestampUs < tokenDurationUs) throw badRequest(
- "Token duration caused arithmetic overflow",
- // FIXME: need dedicate EC (?)
+ "Token duration caused arithmetic overflow", // FIXME: need
dedicate EC (?)
talerErrorCode = TalerErrorCode.TALER_EC_END
)
val token = BearerToken(
@@ -76,7 +75,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
if (!db.bearerTokenCreate(token)) throw internalServerError("Failed at
inserting new token in the database")
call.respond(
TokenSuccessResponse(
- access_token = Base32Crockford.encode(tokenBytes), expiration
= Timestamp(
+ access_token = Base32Crockford.encode(tokenBytes), expiration
= TalerProtocolTimestamp(
t_s = expirationTimestampUs / 1000000L
)
)
@@ -84,8 +83,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
return@post
}
- post("/accounts") {
- // check if only admin is allowed to create new accounts
+ post("/accounts") { // check if only admin is allowed to create new
accounts
if (ctx.restrictRegistration) {
val customer: Customer? = call.authenticateBankRequest(db,
TokenScope.readwrite)
if (customer == null || customer.login != "admin") throw
LibeufinBankException(
@@ -94,30 +92,29 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
hint = "Either 'admin' not authenticated or an ordinary
user tried this operation."
)
)
- }
- // auth passed, proceed with activity.
- val req = call.receive<RegisterAccountRequest>()
- // Prohibit reserved usernames:
+ } // auth passed, proceed with activity.
+ val req = call.receive<RegisterAccountRequest>() // Prohibit reserved
usernames:
if (req.username == "admin" || req.username == "bank") throw
LibeufinBankException(
httpStatus = HttpStatusCode.Conflict, talerError = TalerError(
code = GENERIC_UNDEFINED, // FIXME: this waits GANA.
hint = "Username '${req.username}' is reserved."
)
- )
- // Checking idempotency.
- val maybeCustomerExists = db.customerGetFromLogin(req.username)
- // Can be null if previous call crashed before completion.
+ ) // Checking idempotency.
+ val maybeCustomerExists =
+ db.customerGetFromLogin(req.username) // Can be null if previous
call crashed before completion.
val maybeHasBankAccount = maybeCustomerExists.run {
if (this == null) return@run null
db.bankAccountGetFromOwnerId(this.expectRowId())
}
if (maybeCustomerExists != null && maybeHasBankAccount != null) {
- logger.debug("Registering username was found:
${maybeCustomerExists.login}")
- // Checking _all_ the details are the same.
+ logger.debug("Registering username was found:
${maybeCustomerExists.login}") // Checking _all_ the details are the same.
val isIdentic =
- maybeCustomerExists.name == req.name &&
maybeCustomerExists.email == req.challenge_contact_data?.email &&
maybeCustomerExists.phone == req.challenge_contact_data?.phone &&
maybeCustomerExists.cashoutPayto == req.cashout_payto_uri && CryptoUtil.checkpw(
- req.password,
- maybeCustomerExists.passwordHash
+ maybeCustomerExists.name == req.name &&
+ maybeCustomerExists.email ==
req.challenge_contact_data?.email &&
+ maybeCustomerExists.phone ==
req.challenge_contact_data?.phone &&
+ maybeCustomerExists.cashoutPayto ==
+ req.cashout_payto_uri && CryptoUtil.checkpw(
+ req.password, maybeCustomerExists.passwordHash
) && maybeHasBankAccount.isPublic == req.is_public &&
maybeHasBankAccount.isTalerExchange == req.is_taler_exchange &&
maybeHasBankAccount.internalPaytoUri == req.internal_payto_uri
if (isIdentic) {
call.respond(HttpStatusCode.Created)
@@ -129,26 +126,27 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
hint = "Idempotency check failed."
)
)
- }
- // From here: fresh user being added.
+ } // From here: fresh user being added.
val newCustomer = Customer(
login = req.username,
name = req.name,
email = req.challenge_contact_data?.email,
phone = req.challenge_contact_data?.phone,
- cashoutPayto = req.cashout_payto_uri,
- // Following could be gone, if included in cashout_payto_uri
+ cashoutPayto = req.cashout_payto_uri, // Following could be gone,
if included in cashout_payto_uri
cashoutCurrency = ctx.cashoutCurrency,
passwordHash = CryptoUtil.hashpw(req.password),
)
val newCustomerRowId = db.customerCreate(newCustomer)
- ?: throw internalServerError("New customer INSERT failed despite
the previous checks")/* Crashing here won't break data consistency between
customers
- * and bank accounts, because of the idempotency. Client will
- * just have to retry. */
+ ?: throw internalServerError("New customer INSERT failed despite
the previous checks") // Crashing here won't break data consistency between
customers // and bank accounts, because of the idempotency. Client will //
just have to retry.
val maxDebt = ctx.defaultCustomerDebtLimit
+ val internalPayto: String = if (req.internal_payto_uri != null) {
+ stripIbanPayto(req.internal_payto_uri)
+ } else {
+ stripIbanPayto(genIbanPaytoUri())
+ }
val newBankAccount = BankAccount(
hasDebt = false,
- internalPaytoUri = req.internal_payto_uri ?: genIbanPaytoUri(),
+ internalPaytoUri = internalPayto,
owningCustomerId = newCustomerRowId,
isPublic = req.is_public,
isTalerExchange = req.is_taler_exchange,
@@ -157,12 +155,9 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
val newBankAccountId = db.bankAccountCreate(newBankAccount)
?: throw internalServerError("Could not INSERT bank account
despite all the checks.")
- /**
- * The new account got created, now optionally award the registration
- * bonus to it. The configuration gets either a Taler amount (of the
- * bonus), or null if no bonus is meant to be awarded.
- */
- val bonusAmount = if (ctx.registrationBonusEnabled)
ctx.registrationBonus else null
+ // The new account got created, now optionally award the registration
+ // bonus to it.
+ val bonusAmount = if (ctx.registrationBonusEnabled &&
!req.is_taler_exchange) ctx.registrationBonus else null
if (bonusAmount != null) {
val adminCustomer =
db.customerGetFromLogin("admin") ?: throw
internalServerError("Admin customer not found")
@@ -194,9 +189,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw
unauthorized("Login failed")
val resourceName = call.maybeUriComponent("USERNAME") ?: throw
badRequest(
hint = "No username found in the URI", talerErrorCode =
TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
- )
- // Checking resource name only if Basic auth was used.
- // Successful tokens do not need this check, they just pass.
+ ) // Checking resource name only if Basic auth was used. // Successful
tokens do not need this check, they just pass.
if (((c.login != resourceName) && (c.login != "admin")) &&
(call.getAuthToken() == null)) throw forbidden("No rights on the resource.")
val customerData = db.customerGetFromLogin(c.login)
?: throw internalServerError("Customer '${c.login} despite being
authenticated.'")
@@ -205,8 +198,11 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
val bankAccountData = db.bankAccountGetFromOwnerId(customerInternalId)
?: throw internalServerError("Customer '${c.login} had no bank
account despite they are customer.'")
val balance = Balance(
- amount = bankAccountData.balance.toString(),
- credit_debit_indicator = if (bankAccountData.hasDebt) { "debit" }
else { "credit" }
+ amount = bankAccountData.balance.toString(),
credit_debit_indicator = if (bankAccountData.hasDebt) {
+ "debit"
+ } else {
+ "credit"
+ }
)
call.respond(
AccountData(
@@ -224,12 +220,11 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
}
post("/accounts/{USERNAME}/withdrawals") {
- val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?:
throw unauthorized()
- // Admin not allowed to withdraw in the name of customers:
+ val c = call.authenticateBankRequest(db, TokenScope.readwrite)
+ ?: throw unauthorized() // Admin not allowed to withdraw in the
name of customers:
val accountName = call.expectUriComponent("USERNAME")
if (c.login != accountName) throw unauthorized("User ${c.login} not
allowed to withdraw for account '${accountName}'")
- val req = call.receive<BankAccountCreateWithdrawalRequest>()
- // Checking that the user has enough funds.
+ val req = call.receive<BankAccountCreateWithdrawalRequest>() //
Checking that the user has enough funds.
val b = db.bankAccountGetFromOwnerId(c.expectRowId())
?: throw internalServerError("Customer '${c.login}' lacks bank
account.")
val withdrawalAmount = parseTalerAmount(req.amount)
@@ -239,8 +234,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
) throw forbidden(
hint = "Insufficient funds to withdraw with Taler",
talerErrorCode = TalerErrorCode.TALER_EC_NONE // FIXME: need EC.
- )
- // Auth and funds passed, create the operation now!
+ ) // Auth and funds passed, create the operation now!
val opId = UUID.randomUUID()
if (!db.talerWithdrawalCreate(
opId, b.expectRowId(), withdrawalAmount
@@ -272,13 +266,11 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
}
post("/withdrawals/{withdrawal_id}/abort") {
- val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
- // Idempotency:
+ val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
// Idempotency:
if (op.aborted) {
call.respondText("{}", ContentType.Application.Json)
return@post
- }
- // Op is found, it'll now fail only if previously confirmed (DB
checks).
+ } // Op is found, it'll now fail only if previously confirmed (DB
checks).
if (!db.talerWithdrawalAbort(op.withdrawalUuid)) throw conflict(
hint = "Cannot abort confirmed withdrawal", talerEc =
TalerErrorCode.TALER_EC_END
)
@@ -287,25 +279,22 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
}
post("/withdrawals/{withdrawal_id}/confirm") {
- val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
- // Checking idempotency:
+ val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
// Checking idempotency:
if (op.confirmationDone) {
call.respondText("{}", ContentType.Application.Json)
return@post
}
if (op.aborted) throw conflict(
hint = "Cannot confirm an aborted withdrawal", talerEc =
TalerErrorCode.TALER_EC_BANK_CONFIRM_ABORT_CONFLICT
- )
- // Checking that reserve GOT indeed selected.
+ ) // Checking that reserve GOT indeed selected.
if (!op.selectionDone) throw LibeufinBankException(
httpStatus = HttpStatusCode.UnprocessableEntity, talerError =
TalerError(
hint = "Cannot confirm an unselected withdrawal", code =
TalerErrorCode.TALER_EC_END.code
)
- )/* Confirmation conditions are all met, now put the operation
- * to the selected state _and_ wire the funds to the exchange.
- * Note: 'when' helps not to omit more result codes, should more
- * be added.
- */
+ ) // Confirmation conditions are all met, now put the operation
+ // to the selected state _and_ wire the funds to the exchange.
+ // Note: 'when' helps not to omit more result codes, should more
+ // be added.
when (db.talerWithdrawalConfirm(op.withdrawalUuid, getNowUs())) {
WithdrawalConfirmationResult.BALANCE_INSUFFICIENT -> throw
conflict(
"Insufficient funds", TalerErrorCode.TALER_EC_END // FIXME:
define EC for this.
@@ -340,10 +329,8 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
get("/accounts/{USERNAME}/transactions") {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw
unauthorized()
val resourceName = call.expectUriComponent("USERNAME")
- if (c.login != resourceName && c.login != "admin") throw forbidden()
- // Collecting params.
- val historyParams = getHistoryParams(call.request)
- // Making the query.
+ if (c.login != resourceName && c.login != "admin") throw forbidden()
// Collecting params.
+ val historyParams = getHistoryParams(call.request) // Making the query.
val bankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
?: throw internalServerError("Customer '${c.login}' lacks bank
account.")
val bankAccountId = bankAccount.expectRowId()
@@ -373,17 +360,14 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
// Creates a bank transaction.
post("/accounts/{USERNAME}/transactions") {
val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?:
throw unauthorized()
- val resourceName = call.expectUriComponent("USERNAME")
- // admin has no rights here.
+ val resourceName = call.expectUriComponent("USERNAME") // admin has no
rights here.
if ((c.login != resourceName) && (call.getAuthToken() == null)) throw
forbidden()
val txData = call.receive<BankAccountTransactionCreate>()
- // FIXME: make payto parser IBAN-agnostic?
val payto = parsePayto(txData.payto_uri) ?: throw badRequest("Invalid
creditor Payto")
- val paytoWithoutParams = "payto://iban/${payto.bic}/${payto.iban}"
+ val paytoWithoutParams = stripIbanPayto(txData.payto_uri)
val subject = payto.message ?: throw badRequest("Wire transfer lacks
subject")
- val debtorId = c.dbRowId ?: throw internalServerError("Debtor database
ID not found")
- // This performs already a SELECT on the bank account,
- // like the wire transfer will do as well later!
+ val debtorId = c.dbRowId
+ ?: throw internalServerError("Debtor database ID not found") //
This performs already a SELECT on the bank account, // like the wire transfer
will do as well later!
val creditorCustomerData =
db.bankAccountGetFromInternalPayto(paytoWithoutParams) ?: throw notFound(
"Creditor account not found", TalerErrorCode.TALER_EC_END //
FIXME: define this EC.
)
@@ -415,10 +399,8 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
get("/accounts/{USERNAME}/transactions/{T_ID}") {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw
unauthorized()
- val accountOwner = call.expectUriComponent("USERNAME")
- // auth ok, check rights.
- if (c.login != "admin" && c.login != accountOwner) throw forbidden()
- // rights ok, check tx exists.
+ val accountOwner = call.expectUriComponent("USERNAME") // auth ok,
check rights.
+ if (c.login != "admin" && c.login != accountOwner) throw forbidden()
// rights ok, check tx exists.
val tId = call.expectUriComponent("T_ID")
val txRowId = try {
tId.toLong()
@@ -432,8 +414,7 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx:
BankApplicationContext) {
)
val customerBankAccount = db.bankAccountGetFromOwnerId(customerRowId)
?: throw internalServerError("Customer '${c.login}' lacks bank
account.")
- if (tx.bankAccountId != customerBankAccount.bankAccountId) throw
forbidden("Client has no rights over the bank transaction: $tId")
- // auth and rights, respond.
+ if (tx.bankAccountId != customerBankAccount.bankAccountId) throw
forbidden("Client has no rights over the bank transaction: $tId") // auth and
rights, respond.
call.respond(
BankAccountTransactionInfo(
amount =
"${tx.amount.currency}:${tx.amount.value}.${tx.amount.frac}",
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 44d0b61a..68ef6038 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -34,7 +34,6 @@ import kotlin.math.abs
private const val DB_CTR_LIMIT = 1000000
-
fun Customer.expectRowId(): Long = this.dbRowId ?: throw
internalServerError("Cutsomer '$login' had no DB row ID.")
fun BankAccount.expectBalance(): TalerAmount = this.balance ?: throw
internalServerError("Bank account '${this.internalPaytoUri}' lacks balance.")
fun BankAccount.expectRowId(): Long = this.bankAccountId ?: throw
internalServerError("Bank account '${this.internalPaytoUri}' lacks database row
ID.")
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
index 51db01f2..499bf07e 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
@@ -27,20 +27,19 @@ import io.ktor.server.response.*
import io.ktor.server.routing.*
import net.taler.common.errorcodes.TalerErrorCode
import tech.libeufin.util.getBaseUrl
+import tech.libeufin.util.stripIbanPayto
fun Routing.talerIntegrationHandlers(db: Database, ctx:
BankApplicationContext) {
get("/taler-integration/config") {
val internalCurrency: String = ctx.currency
call.respond(TalerIntegrationConfigResponse(currency =
internalCurrency))
return@get
- }
- // Note: wopid acts as an authentication token.
+ } // Note: wopid acts as an authentication token.
get("/taler-integration/withdrawal-operation/{wopid}") {
val wopid = call.expectUriComponent("wopid")
val op = getWithdrawal(db, wopid) // throws 404 if not found.
val relatedBankAccount =
db.bankAccountGetFromOwnerId(op.walletBankAccount)
- if (relatedBankAccount == null)
- throw internalServerError("Bank has a withdrawal not related to
any bank account.")
+ if (relatedBankAccount == null) throw internalServerError("Bank has a
withdrawal not related to any bank account.")
val suggestedExchange = ctx.suggestedWithdrawalExchange
val walletCustomer =
db.customerGetFromRowId(relatedBankAccount.owningCustomerId)
if (walletCustomer == null)
@@ -65,31 +64,23 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx:
BankApplicationContext)
val wopid = call.expectUriComponent("wopid")
val req = call.receive<BankWithdrawalOperationPostRequest>()
val op = getWithdrawal(db, wopid) // throws 404 if not found.
- if (op.selectionDone) {
- // idempotency
- if (op.selectedExchangePayto != req.selected_exchange &&
- op.reservePub != req.reserve_pub)
- throw conflict(
- hint = "Cannot select different exchange and reserve pub.
under the same withdrawal operation",
- talerEc =
TalerErrorCode.TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT
- )
+ if (op.selectionDone) { // idempotency
+ if (op.selectedExchangePayto != req.selected_exchange &&
op.reservePub != req.reserve_pub) throw conflict(
+ hint = "Cannot select different exchange and reserve pub.
under the same withdrawal operation",
+ talerEc =
TalerErrorCode.TALER_EC_BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT
+ )
}
- val dbSuccess: Boolean = if (!op.selectionDone) {
- // Check if reserve pub. was used in _another_ withdrawal.
- if (db.bankTransactionCheckExists(req.reserve_pub) != null)
- throw conflict(
- "Reserve pub. already used",
- TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
- )
- val exchangePayto = req.selected_exchange
+ val dbSuccess: Boolean = if (!op.selectionDone) { // Check if reserve
pub. was used in _another_ withdrawal.
+ if (db.bankTransactionCheckExists(req.reserve_pub) != null) throw
conflict(
+ "Reserve pub. already used",
TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
+ )
+ val exchangePayto = stripIbanPayto(req.selected_exchange)
db.talerWithdrawalSetDetails(
- op.withdrawalUuid,
- exchangePayto,
- req.reserve_pub
+ op.withdrawalUuid, exchangePayto, req.reserve_pub
)
- }
- else // DB succeeded in the past.
+ } else { // Nothing to do in the database, i.e. we were successful
true
+ }
if (!dbSuccess)
// Whatever the problem, the bank missed it: respond 500.
throw internalServerError("Bank failed at selecting the
withdrawal.")
@@ -99,12 +90,9 @@ fun Routing.talerIntegrationHandlers(db: Database, ctx:
BankApplicationContext)
baseUrl = ctx.spaCaptchaURL,
wopId = wopid
)
- }
- else
- null
+ } else null
val resp = BankWithdrawalOperationPostResponse(
- transfer_done = op.confirmationDone,
- confirm_transfer_url = confirmUrl
+ transfer_done = op.confirmationDone, confirm_transfer_url =
confirmUrl
)
call.respond(resp)
return@post
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 8d4c4a2f..53a2df43 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -435,7 +435,12 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP
server", name = "serve")
val db = Database(dbConnStr, ctx.currency)
if (!maybeCreateAdminAccount(db, ctx)) // logs provided by the helper
exitProcess(1)
- embeddedServer(Netty, port = servePort) {
+ embeddedServer(Netty, port = servePort, configure = {
+ // Disable threads for now, the DB isn't thread safe yet.
+ connectionGroupSize = 1
+ workerGroupSize = 1
+ callGroupSize = 1
+ }) {
corebankWebApp(db, ctx)
}.start(wait = true)
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
index dda207b5..9c5cd9af 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
@@ -56,13 +56,15 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx:
BankApplicationContext)
}
val resp = IncomingHistory(credit_account =
bankAccount.internalPaytoUri)
history.forEach {
- resp.incoming_transactions.add(IncomingReserveTransaction(
- row_id = it.expectRowId(),
- amount = it.amount.toString(),
- date = it.transactionDate,
- debit_account = it.debtorPaytoUri,
- reserve_pub = it.subject
- ))
+ resp.incoming_transactions.add(
+ IncomingReserveTransaction(
+ row_id = it.expectRowId(),
+ amount = it.amount.toString(),
+ date =
TalerProtocolTimestamp.fromMicroseconds(it.transactionDate),
+ debit_account = it.debtorPaytoUri,
+ reserve_pub = it.subject
+ )
+ )
}
call.respond(resp)
return@get
@@ -81,10 +83,12 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx:
BankApplicationContext)
&& maybeDoneAlready.exchangeBaseUrl ==
req.exchange_base_url
&& maybeDoneAlready.wtid == req.wtid
if (isIdempotent) {
- call.respond(TransferResponse(
- timestamp = maybeDoneAlready.timestamp,
- row_id = maybeDoneAlready.debitTxRowId
- ))
+ call.respond(
+ TransferResponse(
+ timestamp =
TalerProtocolTimestamp.fromMicroseconds(maybeDoneAlready.timestamp),
+ row_id = maybeDoneAlready.debitTxRowId
+ )
+ )
return@post
}
throw conflict(
@@ -116,10 +120,12 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx:
BankApplicationContext)
)
val debitRowId = dbRes.txRowId
?: throw internalServerError("Database did not return the debit tx
row ID")
- call.respond(TransferResponse(
- timestamp = transferTimestamp,
- row_id = debitRowId
- ))
+ call.respond(
+ TransferResponse(
+ timestamp =
TalerProtocolTimestamp.fromMicroseconds(transferTimestamp),
+ row_id = debitRowId
+ )
+ )
return@post
}
@@ -169,8 +175,9 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx:
BankApplicationContext)
call.respond(
AddIncomingResponse(
row_id = rowId,
- timestamp = txTimestamp
- ))
+ timestamp =
TalerProtocolTimestamp.fromMicroseconds(txTimestamp)
+ )
+ )
return@post
}
}
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 87d73985..176ec63b 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -290,6 +290,46 @@ class LibeuFinApiTest {
}
}
+ /**
+ * Testing the account creation and its idempotency
+ */
+ @Test
+ fun createTwoAccountsTest() {
+ testApplication {
+ val db = initDb()
+ val ctx = getTestContext()
+ val ibanPayto = genIbanPaytoUri()
+ application {
+ corebankWebApp(db, ctx)
+ }
+ var resp = client.post("/accounts") {
+ expectSuccess = false
+ contentType(ContentType.Application.Json)
+ setBody(
+ """{
+ "username": "foo",
+ "password": "bar",
+ "name": "Jane"
+ }""".trimIndent()
+ )
+ }
+ assert(resp.status == HttpStatusCode.Created)
+ // Test creating another account.
+ resp = client.post("/accounts") {
+ expectSuccess = false
+ contentType(ContentType.Application.Json)
+ setBody(
+ """{
+ "username": "joe",
+ "password": "bar",
+ "name": "Joe"
+ }""".trimIndent()
+ )
+ }
+ assert(resp.status == HttpStatusCode.Created)
+ }
+ }
+
/**
* Test admin-only account creation
*/
diff --git a/util/src/main/kotlin/Payto.kt b/util/src/main/kotlin/IbanPayto.kt
similarity index 87%
rename from util/src/main/kotlin/Payto.kt
rename to util/src/main/kotlin/IbanPayto.kt
index 026aecc3..daa6872c 100644
--- a/util/src/main/kotlin/Payto.kt
+++ b/util/src/main/kotlin/IbanPayto.kt
@@ -1,13 +1,12 @@
package tech.libeufin.util
-import io.ktor.http.*
import logger
import java.net.URI
import java.net.URLDecoder
import java.net.URLEncoder
// Payto information.
-data class Payto(
+data class IbanPayto(
// represent query param "sender-name" or "receiver-name".
val receiverName: String?,
val iban: String,
@@ -27,7 +26,7 @@ private fun getQueryParamOrNull(name: String, params:
List<Pair<String, String>>
}
// Parses a Payto URI, returning null if the input is invalid.
-fun parsePayto(payto: String): Payto? {
+fun parsePayto(payto: String): IbanPayto? {
/**
* This check is due because URIs having a "payto:" prefix without
* slashes are correctly parsed by the Java 'URI' class. 'mailto'
@@ -74,7 +73,7 @@ fun parsePayto(payto: String): Payto? {
}
} else null
- return Payto(
+ return IbanPayto(
iban = iban,
bic = bic,
amount = getQueryParamOrNull("amount", params),
@@ -96,4 +95,17 @@ fun buildIbanPaytoUri(
return "$ret&message=$messageUrlEnc"
}
return ret
-}
\ No newline at end of file
+}
+
+/**
+ * Strip a payto://iban URI of everything
+ * except the IBAN.
+ */
+fun stripIbanPayto(paytoUri: String): String {
+ val parsedPayto = parsePayto(paytoUri)
+ if (parsedPayto == null) {
+ throw Error("invalid payto://iban URI")
+ }
+ val canonIban = parsedPayto.iban.lowercase()
+ return "payto://iban/${canonIban}"
+}
diff --git a/util/src/test/kotlin/PaytoTest.kt
b/util/src/test/kotlin/PaytoTest.kt
index c7174883..18fcb41b 100644
--- a/util/src/test/kotlin/PaytoTest.kt
+++ b/util/src/test/kotlin/PaytoTest.kt
@@ -1,5 +1,5 @@
import org.junit.Test
-import tech.libeufin.util.Payto
+import tech.libeufin.util.IbanPayto
import tech.libeufin.util.parsePayto
class PaytoTest {
@@ -13,7 +13,7 @@ class PaytoTest {
@Test
fun parsePaytoTest() {
- val withBic: Payto =
parsePayto("payto://iban/BIC123/IBAN123?receiver-name=The%20Name")!!
+ val withBic: IbanPayto =
parsePayto("payto://iban/BIC123/IBAN123?receiver-name=The%20Name")!!
assert(withBic.iban == "IBAN123")
assert(withBic.bic == "BIC123")
assert(withBic.receiverName == "The Name")
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: fix timestamp format, normalize internal IBANs,
gnunet <=