diff options
author | Antoine A <> | 2024-01-17 13:03:26 +0000 |
---|---|---|
committer | Antoine A <> | 2024-01-17 13:03:26 +0000 |
commit | 5efc69c41f736fc383e78567fd2446792525420f (patch) | |
tree | 79e3a54f76e471e1ce7c5571b8f6ece6f7e7d572 | |
parent | 1aa936208e54bdc8afd0a57070a864a1b044c312 (diff) | |
download | libeufin-5efc69c41f736fc383e78567fd2446792525420f.tar.gz libeufin-5efc69c41f736fc383e78567fd2446792525420f.tar.bz2 libeufin-5efc69c41f736fc383e78567fd2446792525420f.zip |
receiver-name in cashout payto
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt | 3 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 119 | ||||
-rw-r--r-- | bank/src/test/kotlin/CoreBankApiTest.kt | 61 |
3 files changed, 121 insertions, 62 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt index e37a1661..5f8830cb 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt @@ -454,14 +454,17 @@ class IbanPayTo: PaytoUri { receiverName = params["receiver-name"] } + /** Canonical IBAN payto with receiver-name parameter if present */ fun maybeFull(): String { return canonical + if (receiverName != null) ("?receiver-name=" + receiverName.encodeURLParameter()) else "" } + /** Canonical IBAN payto with receiver-name parameter, fail if absent */ fun expectFull(): String { return canonical + "?receiver-name=" + receiverName!!.encodeURLParameter() } + /** Canonical IBAN payto with receiver-name parameter set to [defaultName] if absent */ fun fullOptName(defaultName: String): String { return canonical + "?receiver-name=" + (receiverName ?: defaultName).encodeURLParameter() } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt index ac9b6a96..e9e30c52 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -240,83 +240,88 @@ class AccountDAO(private val db: Database) { val checkCashout = !isAdmin && !allowEditCashout && cashoutPayto.isSome() val checkDebtLimit = !isAdmin && debtLimit != null - // Get user ID and check reconfig rights TODO checkout with name - val (customerId, currChannel, currInfo) = conn.prepareStatement(""" - SELECT - customer_id - ,(${ if (checkName) "name != ?" else "false" }) as name_change - ,(${ if (checkCashout) "cashout_payto IS DISTINCT FROM ?" else "false" }) as cashout_change - ,(${ if (checkDebtLimit) "max_debt != (?, ?)::taler_amount" else "false" }) as debt_limit_change - ,(${ when (tan_channel.get()) { - null -> "false" - TanChannel.sms -> if (phone.get() != null) "false" else "phone IS NULL" - TanChannel.email -> if (email.get() != null) "false" else "email IS NULL" - }}) as missing_tan_info - ,tan_channel, phone, email + data class CurrentAccount( + val id: Long, + val channel: TanChannel?, + val email: String?, + val phone: String?, + val name: String, + val cashoutPayTo: String?, + val debtLimit: TalerAmount, + ) + + // Get user ID and current data + val curr = conn.prepareStatement(""" + SELECT + customer_id, tan_channel, phone, email, name, cashout_payto + ,(max_debt).val AS max_debt_val + ,(max_debt).frac AS max_debt_frac FROM customers JOIN bank_accounts ON customer_id=owning_customer_id WHERE login=? """).run { - var idx = 1; - if (checkName) { - setString(idx, name); idx++ - } - if (checkCashout) { - setString(idx, cashoutPayto.get()?.maybeFull()); idx++ // TODO cashout with name - } - if (checkDebtLimit) { - setLong(idx, debtLimit!!.value); idx++ - setInt(idx, debtLimit.frac); idx++ - } - setString(idx, login) - executeQuery().use { - when { - !it.next() -> return@transaction AccountPatchResult.UnknownAccount - it.getBoolean("name_change") -> return@transaction AccountPatchResult.NonAdminName - it.getBoolean("cashout_change") -> return@transaction AccountPatchResult.NonAdminCashout - it.getBoolean("debt_limit_change") -> return@transaction AccountPatchResult.NonAdminDebtLimit - it.getBoolean("missing_tan_info") -> return@transaction AccountPatchResult.MissingTanInfo - else -> { - val currChannel = it.getString("tan_channel")?.run { TanChannel.valueOf(this) } - Triple( - it.getLong("customer_id"), - currChannel, - when (tan_channel.get() ?: currChannel) { - TanChannel.sms -> it.getString("phone") - TanChannel.email -> it.getString("email") - null -> null - } - ) - } - } - } + setString(1, login) + oneOrNull { + CurrentAccount( + id = it.getLong("customer_id"), + channel = it.getString("tan_channel")?.run { TanChannel.valueOf(this) }, + phone = it.getString("phone"), + email = it.getString("email"), + name = it.getString("name"), + cashoutPayTo = it.getString("cashout_payto"), + debtLimit = it.getAmount("max_debt", db.bankCurrency), + ) + } ?: return@transaction AccountPatchResult.UnknownAccount } - - val newChannel = tan_channel.get(); - val newInfo = when (newChannel ?: currChannel) { + + // Patched TAN channel + val patchChannel = tan_channel.get() + // TAN channel after the PATCH + val newChannel = patchChannel ?: curr.channel + // Patched TAN info + val patchInfo = when (newChannel) { TanChannel.sms -> phone.get() TanChannel.email -> email.get() null -> null } + // TAN info after the PATCH + val newInfo = patchInfo ?: when (newChannel) { + TanChannel.sms -> curr.phone + TanChannel.email -> curr.email + null -> null + } + // Cashout payto with a receiver-name using if receiver-name is missing the new named if present or the current one + val cashoutPaytoNamed = cashoutPayto.get()?.fullOptName(name ?: curr.name) + + // Check reconfig rights + if (checkName && name != curr.name) + return@transaction AccountPatchResult.NonAdminName + if (checkCashout && cashoutPaytoNamed != curr.cashoutPayTo) + return@transaction AccountPatchResult.NonAdminCashout + if (checkDebtLimit && debtLimit != curr.debtLimit) + return@transaction AccountPatchResult.NonAdminDebtLimit + if (patchChannel != null && newInfo == null) + return@transaction AccountPatchResult.MissingTanInfo + // Tan channel verification if (!isAdmin) { // Check performed 2fa check - if (currChannel != null && !is2fa) { + if (curr.channel != null && !is2fa) { // Perform challenge with current settings return@transaction AccountPatchResult.TanRequired(channel = null, info = null) } // If channel or info changed and the 2fa challenge is performed with old settings perform a new challenge with new settings - if ((newChannel != null && newChannel != faChannel) || (newInfo != null && newInfo != faInfo)) { - return@transaction AccountPatchResult.TanRequired(channel = newChannel ?: currChannel, info = newInfo ?: currInfo) + if ((patchChannel != null && patchChannel != faChannel) || (patchInfo != null && patchInfo != faInfo)) { + return@transaction AccountPatchResult.TanRequired(channel = newChannel, info = newInfo) } } // Invalidate current challenges - if (newChannel != null || newInfo != null) { + if (patchChannel != null || patchInfo != null) { val stmt = conn.prepareStatement("UPDATE tan_challenges SET expiration_date=0 WHERE customer=?") - stmt.setLong(1, customerId) + stmt.setLong(1, curr.id) stmt.execute() } @@ -331,7 +336,7 @@ class AccountDAO(private val db: Database) { sequence { isPublic?.let { yield(it) } debtLimit?.let { yield(it.value); yield(it.frac) } - yield(customerId) + yield(curr.id) } ) @@ -347,12 +352,12 @@ class AccountDAO(private val db: Database) { }, "WHERE customer_id = ?", sequence { - cashoutPayto.some { yield(it?.canonical) } + cashoutPayto.some { yield(cashoutPaytoNamed) } phone.some { yield(it) } email.some { yield(it) } tan_channel.some { yield(it?.name) } name?.let { yield(it) } - yield(customerId) + yield(curr.id) } ) diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt index 5a031b87..c9ecfa03 100644 --- a/bank/src/test/kotlin/CoreBankApiTest.kt +++ b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -195,7 +195,7 @@ class CoreBankAccountsApiTest { } // Check given payto - val ibanPayto = genIbanPaytoUri() + val ibanPayto = IbanPayTo(genIbanPaytoUri()) val req = obj { "username" to "foo" "password" to "password" @@ -208,7 +208,7 @@ class CoreBankAccountsApiTest { client.post("/accounts") { json(req) }.assertOkJson<RegisterAccountResponse> { - assertEquals(ibanPayto, it.internal_payto_uri) + assertEquals(ibanPayto.canonical, it.internal_payto_uri) } // Testing idempotency client.post("/accounts") { @@ -298,6 +298,31 @@ class CoreBankAccountsApiTest { client.get("/accounts/bar") { pwAuth("admin") }.assertNotFound(TalerErrorCode.BANK_UNKNOWN_ACCOUNT) + + // Check cashout payto receiver name logic + client.post("/accounts") { + json { + "username" to "cashout_guess" + "password" to "cashout_guess-password" + "name" to "Mr Guess My Name" + "cashout_payto_uri" to ibanPayto + } + }.assertOk() + client.getA("/accounts/cashout_guess").assertOkJson<AccountData> { + assertEquals(ibanPayto.fullOptName("Mr Guess My Name"), it.cashout_payto_uri) + } + val full = ibanPayto.fullOptName("Santa Claus") + client.post("/accounts") { + json { + "username" to "cashout_keep" + "password" to "cashout_keep-password" + "name" to "Mr Keep My Name" + "cashout_payto_uri" to full + } + }.assertOk() + client.getA("/accounts/cashout_keep").assertOkJson<AccountData> { + assertEquals(full, it.cashout_payto_uri) + } } // Test account created with bonus @@ -465,7 +490,7 @@ class CoreBankAccountsApiTest { // Successful attempt now val cashout = IbanPayTo(genIbanPaytoUri()) val req = obj { - "cashout_payto_uri" to cashout.canonical + "cashout_payto_uri" to cashout "name" to "Roger" "is_public" to true "contact_data" to obj { @@ -496,7 +521,7 @@ class CoreBankAccountsApiTest { // Check patch client.getA("/accounts/merchant").assertOkJson<AccountData> { obj -> assertEquals("Roger", obj.name) - assertEquals(cashout.canonical, obj.cashout_payto_uri) + assertEquals(cashout.fullOptName(obj.name), obj.cashout_payto_uri) assertEquals("+99", obj.contact_data?.phone?.get()) assertEquals("foo@example.com", obj.contact_data?.email?.get()) assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold) @@ -510,7 +535,7 @@ class CoreBankAccountsApiTest { }.assertNoContent() client.getA("/accounts/merchant").assertOkJson<AccountData> { obj -> assertEquals("Roger", obj.name) - assertEquals(cashout.canonical, obj.cashout_payto_uri) + assertEquals(cashout.fullOptName(obj.name), obj.cashout_payto_uri) assertEquals("+99", obj.contact_data?.phone?.get()) assertEquals("foo@example.com", obj.contact_data?.email?.get()) assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold) @@ -525,6 +550,32 @@ class CoreBankAccountsApiTest { } }.assertConflict(TalerErrorCode.END) + // Check cashout payto receiver name logic + client.post("/accounts") { + json { + "username" to "cashout" + "password" to "cashout-password" + "name" to "Mr Cashout Cashout" + } + }.assertOk() + for ((cashout, name, expect) in listOf( + Triple(cashout.canonical, null, cashout.fullOptName("Mr Cashout Cashout")), + Triple(cashout.canonical, "New name", cashout.fullOptName("New name")), + Triple(cashout.fullOptName("Full name"), null, cashout.fullOptName("Full name")), + Triple(cashout.fullOptName("Full second name"), "Another name", cashout.fullOptName("Full second name")) + )) { + client.patch("/accounts/cashout") { + pwAuth("admin") + json { + "cashout_payto_uri" to cashout + if (name != null) "name" to name + } + }.assertNoContent() + client.getA("/accounts/cashout").assertOkJson<AccountData> { obj -> + assertEquals(expect, obj.cashout_payto_uri) + } + } + // Check 2FA fillTanInfo("merchant") client.patchA("/accounts/merchant") { |