commit a657794c0861a0000d073193328d810e2f6b0f8e
parent 3a4c4b61ef871788252b7a258aa94aa82db9670f
Author: Antoine A <>
Date: Tue, 26 Dec 2023 16:14:27 +0000
Improve testing and error
Diffstat:
5 files changed, 130 insertions(+), 19 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -200,6 +200,10 @@ suspend fun patchAccount(db: Database, ctx: BankConfig, req: AccountReconfigurat
"'admin' account cannot be public",
TalerErrorCode.END
)
+
+ if (req.tan_channel is Option.Some && req.tan_channel.value != null && ctx.tanChannels.get(req.tan_channel.value ) == null) {
+ throw unsupportedTanChannel(req.tan_channel.value)
+ }
return db.account.reconfig(
login = username,
@@ -707,11 +711,8 @@ private fun Routing.coreBankTanApi(db: Database, ctx: BankConfig) {
)
is TanSendResult.Success -> {
res.tanCode?.run {
- val tanScript = ctx.tanChannels.get(res.tanChannel) ?: throw libeufinError(
- HttpStatusCode.NotImplemented,
- "Unsupported tan channel ${res.tanChannel}",
- TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED
- )
+ val tanScript = ctx.tanChannels.get(res.tanChannel)
+ ?: throw unsupportedTanChannel(res.tanChannel)
val exitValue = withContext(Dispatchers.IO) {
val process = ProcessBuilder(tanScript, res.tanInfo).start()
try {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
@@ -145,4 +145,11 @@ fun unknownAccount(id: String): LibeufinException {
"Account '$id' not found",
TalerErrorCode.BANK_UNKNOWN_ACCOUNT
)
+}
+
+fun unsupportedTanChannel(channel: TanChannel): LibeufinException {
+ return conflict(
+ "Unsupported tan channel $channel",
+ TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED
+ )
}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -386,7 +386,7 @@ class EditAccount : CliktCommand(
cashout_payto_uri = Option.Some(cashout_payto_uri),
debit_threshold = debit_threshold
)
- when (patchAccount(db, ctx, req, username, true)) {
+ when (patchAccount(db, ctx, req, username, true, false)) {
AccountPatchResult.Success ->
logger.info("Account '$username' edited")
AccountPatchResult.UnknownAccount ->
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -274,7 +274,7 @@ class AccountDAO(private val db: Database) {
it.getBoolean("phone_change") -> return@transaction AccountPatchResult.NonAdminContact
it.getBoolean("email_change") -> return@transaction AccountPatchResult.NonAdminContact
it.getBoolean("missing_tan_info") -> return@transaction AccountPatchResult.MissingTanInfo
- it.getBoolean("tan_required") && !is2fa -> return@transaction AccountPatchResult.TanRequired
+ it.getBoolean("tan_required") && !is2fa && !isAdmin -> return@transaction AccountPatchResult.TanRequired
else -> it.getLong("customer_id")
}
}
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -416,18 +416,11 @@ class CoreBankAccountsApiTest {
}.assertNoContent()
// Check tan info
- client.patchA("/accounts/merchant") {
- json {
- "tan_channel" to "sms"
- "email" to "mail@test.com"
- }
- }.assertErr(TalerErrorCode.BANK_MISSING_TAN_INFO)
- client.patchA("/accounts/merchant") {
- json {
- "tan_channel" to "email"
- "phone" to "+99"
- }
- }.assertErr(TalerErrorCode.BANK_MISSING_TAN_INFO)
+ for (channel in listOf("sms", "email")) {
+ client.patchA("/accounts/merchant") {
+ json { "tan_channel" to channel }
+ }.assertErr(TalerErrorCode.BANK_MISSING_TAN_INFO)
+ }
checkAdminOnly(
obj(req) { "debit_threshold" to "KUDOS:100" },
@@ -527,6 +520,17 @@ class CoreBankAccountsApiTest {
}
}
+ // Test TAN check account patch
+ @Test
+ fun patchNoTan() = bankSetup(conf = "test_no_tan.conf") { _ ->
+ // Check unsupported TAN channel
+ client.patchA("/accounts/customer") {
+ json {
+ "tan_channel" to "sms"
+ }
+ }.assertConflict(TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED)
+ }
+
// PATCH /accounts/USERNAME/auth
@Test
fun passwordChange() = bankSetup { _ ->
@@ -1360,4 +1364,103 @@ class CoreBankCashoutApiTest {
client.get("/accounts/customer/cashouts")
.assertNotImplemented()
}
+}
+
+class CoreBankTanApiTest {
+ // POST /accounts/{USERNAME}/challenge/{challenge_id}
+ @Test
+ fun create() = bankSetup { _ ->
+ authRoutine(HttpMethod.Post, "/accounts/merchant/challenge/42")
+
+ client.patch("/accounts/merchant") {
+ pwAuth("admin")
+ json {
+ "contact_data" to obj {
+ "phone" to "+99"
+ }
+ "tan_channel" to "sms"
+ }
+ }.assertNoContent()
+ client.patchA("/accounts/merchant") {
+ json { "is_public" to false }
+ }.assertAcceptedJson<TanChallenge> {
+ // Check ok
+ client.postA("/accounts/merchant/challenge/${it.challenge_id}")
+ .assertOkJson<TanTransmission> {
+ assertEquals(it.tan_info, "+99")
+ assertEquals(it.tan_channel.name, "sms")
+ }
+ // Check retry
+ client.postA("/accounts/merchant/challenge/${it.challenge_id}")
+ .assertOkJson<TanTransmission> {
+ assertEquals(it.tan_info, "+99")
+ assertEquals(it.tan_channel.name, "sms")
+ }
+ }
+
+ // Check fail
+ client.patch("/accounts/merchant") {
+ pwAuth("admin")
+ json {
+ "contact_data" to obj {
+ "email" to "test@test.com"
+ }
+ "tan_channel" to "email"
+ }
+ }.assertNoContent()
+ client.patchA("/accounts/merchant") {
+ json { "is_public" to false }
+ }.assertAcceptedJson<TanChallenge> {
+ client.postA("/accounts/merchant/challenge/${it.challenge_id}")
+ .assertStatus(HttpStatusCode.BadGateway, TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED)
+ }
+
+ // TODO check what happens when tan info or tan channel change
+
+ // Unknown challenge
+ client.postA("/accounts/merchant/challenge/42")
+ .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
+ }
+
+ // POST /accounts/{USERNAME}/challenge/{challenge_id}/confirm
+ @Test
+ fun confirm() = bankSetup { _ ->
+ authRoutine(HttpMethod.Post, "/accounts/merchant/challenge/42/confirm")
+
+ client.patch("/accounts/merchant") {
+ pwAuth("admin")
+ json {
+ "contact_data" to obj {
+ "phone" to "+99"
+ }
+ "tan_channel" to "sms"
+ }
+ }.assertNoContent()
+ val id = client.patchA("/accounts/merchant") {
+ json { "is_public" to false }
+ }.assertAcceptedJson<TanChallenge>().challenge_id
+ client.postA("/accounts/merchant/challenge/$id").assertOk()
+ val code = smsCode("+99")
+
+ // Check bad TAN code
+ client.postA("/accounts/merchant/challenge/$id/confirm") {
+ json { "tan" to "nice-try" }
+ }.assertConflict(TalerErrorCode.BANK_TAN_CHALLENGE_FAILED)
+
+ // Check OK
+ client.postA("/accounts/merchant/challenge/$id/confirm") {
+ json { "tan" to code }
+ }.assertNoContent()
+ // Check idempotence
+ client.postA("/accounts/merchant/challenge/$id/confirm") {
+ json { "tan" to code }
+ }.assertNoContent()
+
+ // TODO check what happens when tan info or tan channel change
+
+ // Unknown challenge
+ client.postA("/accounts/merchant/challenge/42/confirm") {
+ json { "tan" to code }
+ }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
+ }
}
\ No newline at end of file