commit c15f9dfa5a284dff23acf28d63d34d05a912afcd
parent afb4b8a9fdf93ae2cb0e01e8ba54fb9e9f66d573
Author: MS <ms@taler.net>
Date: Tue, 10 Jan 2023 21:47:59 +0100
Introducing file based TAN channel.
With this feature, Sandbox writes the TAN to a
file under /tmp where it can be read by the tester
without setting up any SMS or e-mail channel.
Diffstat:
4 files changed, 154 insertions(+), 11 deletions(-)
diff --git a/cli/bin/circuit_test.sh b/cli/bin/circuit_test.sh
@@ -21,6 +21,7 @@ echo -n Configure the default demobank...
libeufin-sandbox config default
echo DONE
echo -n Start the bank...
+export LIBEUFIN_SANDBOX_ADMIN_PASSWORD=circuit
libeufin-sandbox serve &> sandbox.log &
SANDBOX_PID=$!
trap "echo -n 'killing the bank (pid $SANDBOX_PID)...'; kill $SANDBOX_PID; wait; echo DONE" EXIT
@@ -33,7 +34,7 @@ curl http://localhost:5000/demobanks/default/circuit-api/config &> /dev/null
echo DONE
echo -n "Register new account..."
export LIBEUFIN_SANDBOX_USERNAME=admin
-export LIBEUFIN_SANDBOX_PASSWORD=secret
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
export LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD=foo
./libeufin-cli \
sandbox --sandbox-url http://localhost:5000/ \
@@ -98,7 +99,7 @@ echo DONE
# balance to zero.
echo -n Bring the account to 0 balance...
export LIBEUFIN_SANDBOX_USERNAME=admin
-export LIBEUFIN_SANDBOX_PASSWORD=secret
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
./libeufin-cli \
sandbox --sandbox-url http://localhost:5000/ \
demobank \
@@ -109,7 +110,7 @@ export LIBEUFIN_SANDBOX_PASSWORD=secret
echo DONE
echo -n Delete the account...
export LIBEUFIN_SANDBOX_USERNAME=admin
-export LIBEUFIN_SANDBOX_PASSWORD=secret
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
./libeufin-cli \
sandbox --sandbox-url http://localhost:5000/ \
demobank \
diff --git a/cli/bin/circuit_test_file_tan.sh b/cli/bin/circuit_test_file_tan.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+
+# Tests successful cases of the CLI acting
+# as the client of the Circuit API.
+
+set -eu
+
+echo TESTING THE CLI SIDE OF THE CIRCUIT API
+jq --version &> /dev/null || (echo "'jq' command not found"; exit 77)
+curl --version &> /dev/null || (echo "'curl' command not found"; exit 77)
+
+DB_PATH=/tmp/circuit-test.sqlite3
+export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:$DB_PATH
+
+echo -n Delete previous data..
+rm -f $DB_PATH
+echo DONE
+echo -n Configure the default demobank...
+libeufin-sandbox config default
+echo DONE
+echo -n Start the bank...
+export LIBEUFIN_SANDBOX_ADMIN_PASSWORD=circuit
+libeufin-sandbox serve &> sandbox.log &
+SANDBOX_PID=$!
+trap "echo -n 'killing the bank (pid $SANDBOX_PID)...'; kill $SANDBOX_PID; wait; echo DONE" EXIT
+echo DONE
+echo -n Wait for the bank...
+curl --max-time 2 --retry-connrefused --retry-delay 1 --retry 10 http://localhost:5000/ &> /dev/null
+echo DONE
+echo Ask Circuit API /config...
+curl http://localhost:5000/demobanks/default/circuit-api/config &> /dev/null
+echo DONE
+echo -n "Register new account..."
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
+export LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD=foo
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-register --name eee --username www \
+ --cashout-address payto://iban/FIAT --internal-iban LOCAL
+echo DONE
+echo -n Reconfigure account specifying a phone number..
+# Give phone number.
+export LIBEUFIN_SANDBOX_USERNAME=www
+export LIBEUFIN_SANDBOX_PASSWORD=foo
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-reconfig --cashout-address payto://iban/WWW --phone +999
+echo DONE
+echo -n Create a cash-out operation...
+CASHOUT_RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout \
+ --tan-channel=file \
+ --amount-debit=EUR:1 \
+ --amount-credit=CHF:0.95
+)
+echo DONE
+echo -n "Extract the cash-out UUID..."
+CASHOUT_UUID=$(echo ${CASHOUT_RESP} | jq --raw-output '.uuid')
+echo DONE
+echo -n Get cash-out details...
+RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-details \
+ --uuid $CASHOUT_UUID
+)
+OPERATION_STATUS=$(echo $RESP | jq --raw-output '.status')
+if ! test "$OPERATION_STATUS" = "PENDING"; then
+ echo Unexpected cash-out operation status found: $OPERATION_STATUS
+ exit 1
+fi
+echo DONE
+echo -n Abort the cash-out operation...
+RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-abort \
+ --uuid $CASHOUT_UUID
+)
+echo DONE
+echo -n Create another cash-out operation...
+CASHOUT_RESP=$(./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout \
+ --tan-channel=file \
+ --amount-debit=EUR:1 \
+ --amount-credit=CHF:0.95
+)
+CASHOUT_UUID=$(echo ${CASHOUT_RESP} | jq --raw-output '.uuid')
+echo DONE
+echo Reading the TAN from /tmp/libeufin-cashout-tan.txt
+INPUT_TAN=$(cat /tmp/libeufin-cashout-tan.txt)
+echo -n Confirm the last cash-out operation...
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-cashout-confirm --uuid $CASHOUT_UUID --tan $INPUT_TAN
+echo DONE
+# The user now has -1 balance. Let the bank
+# award EUR:1 to them, in order to bring their
+# balance to zero.
+echo -n Bring the account to 0 balance...
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ new-transaction \
+ --bank-account admin \
+ --payto-with-subject "payto://iban/SANDBOXX/LOCAL?message=bring-to-zero" \
+ --amount EUR:1
+echo DONE
+echo -n Delete the account...
+export LIBEUFIN_SANDBOX_USERNAME=admin
+export LIBEUFIN_SANDBOX_PASSWORD=circuit
+./libeufin-cli \
+ sandbox --sandbox-url http://localhost:5000/ \
+ demobank \
+ circuit-delete-account --username www
+echo DONE
diff --git a/cli/bin/libeufin-cli b/cli/bin/libeufin-cli
@@ -1863,8 +1863,8 @@ def password_reconfig(obj, username):
)
@click.option(
"--tan-channel",
- help="Indicates how to send the TAN to the user: only 'sms' or 'email' are valid values. If missing, the bank defaults to SMS",
- required=False
+ help="Indicates how to send the TAN to the user: 'sms', 'email' and 'file' are valid values. If missing, the bank defaults to SMS. 'file' makes the server write the TAN to /tmp/libeufin-cashout-tan.txt, normally used for testing.",
+ required=False,
)
@click.pass_obj
def circuit_cashout(obj, subject, amount_debit, amount_credit, tan_channel):
@@ -1878,7 +1878,6 @@ def circuit_cashout(obj, subject, amount_debit, amount_credit, tan_channel):
req.update(subject=subject)
if tan_channel:
req.update(tan_channel=tan_channel)
-
cashout_creation_endpoint = obj.circuit_api_url("cashouts")
try:
resp = post(
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CircuitApi.kt
@@ -8,6 +8,7 @@ import io.ktor.server.routing.*
import org.jetbrains.exposed.sql.transactions.transaction
import tech.libeufin.sandbox.CashoutOperationsTable.uuid
import tech.libeufin.util.*
+import java.io.File
import java.math.BigDecimal
import java.math.MathContext
import java.util.*
@@ -118,9 +119,17 @@ fun generateCashoutSubject(
* NOTE: future versions take the supported TAN method from
* the configuration, or options passed when starting the bank.
*/
-enum class SupportedTanChannels { SMS, EMAIL }
-fun isTanChannelSupported(tanMethod: String): Boolean {
- return listOf(SupportedTanChannels.SMS.name, SupportedTanChannels.EMAIL.name).contains(tanMethod.uppercase())
+const val LIBEUFIN_TAN_TMP_FILE = "/tmp/libeufin-cashout-tan.txt"
+enum class SupportedTanChannels {
+ SMS,
+ EMAIL,
+ FILE // Test channel writing the TAN to the LIBEUFIN_TAN_TMP_FILE location.
+}
+fun isTanChannelSupported(tanChannel: String): Boolean {
+ enumValues<SupportedTanChannels>().forEach {
+ if (tanChannel.uppercase() == it.name) return true
+ }
+ return false
}
fun circuitApi(circuitRoute: Route) {
@@ -243,12 +252,12 @@ fun circuitApi(circuitRoute: Route) {
throw badRequest("The '${req::amount_debit.name}' field has the wrong currency")
if (amountCredit.currency == demobank.currency)
throw badRequest("The '${req::amount_credit.name}' field didn't change the currency.")
- // check if TAN is supported.
+ // check if TAN is supported. Default to SMS, if that's missing.
val tanChannel = req.tan_channel?.uppercase() ?: SupportedTanChannels.SMS.name
if (!isTanChannelSupported(tanChannel))
throw SandboxError(
HttpStatusCode.ServiceUnavailable,
- "TAN method $tanChannel not supported."
+ "TAN channel '$tanChannel' not supported."
)
// check if the user contact data would allow the TAN channel.
val customer = getCustomer(username = user)
@@ -299,6 +308,14 @@ fun circuitApi(circuitRoute: Route) {
SupportedTanChannels.SMS.name -> {
// TBD
}
+ SupportedTanChannels.FILE.name -> {
+ try {
+ File(LIBEUFIN_TAN_TMP_FILE).writeText(op.tan)
+ } catch (e: Exception) {
+ logger.error(e.message)
+ throw internalServerError("File TAN failed: could not write to $LIBEUFIN_TAN_TMP_FILE")
+ }
+ }
else ->
throw internalServerError("The bank didn't catch a unsupported TAN channel: $tanChannel.")
}