summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMS <ms@taler.net>2023-10-04 13:35:34 +0200
committerMS <ms@taler.net>2023-10-04 13:38:29 +0200
commitf022b73168432b7cb425a5a69f41b616ce34829f (patch)
tree2b22e6d08667ee4eb5a070bfafed1aec548471f6
parent377b22e474af8188b5a1e53e68c43f1920b3bcc9 (diff)
downloadlibeufin-f022b73168432b7cb425a5a69f41b616ce34829f.tar.gz
libeufin-f022b73168432b7cb425a5a69f41b616ce34829f.tar.bz2
libeufin-f022b73168432b7cb425a5a69f41b616ce34829f.zip
DB side of account PATCHing
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt29
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt18
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Database.kt39
-rw-r--r--bank/src/test/kotlin/DatabaseTest.kt62
-rw-r--r--database-versioning/procedures.sql51
5 files changed, 184 insertions, 15 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index f5c4bc5c..34b03472 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -742,4 +742,31 @@ data class AccountReconfiguration(
val cashout_address: String?,
val name: String?,
val is_exchange: Boolean
-) \ No newline at end of file
+)
+
+/**
+ * This type expresses the outcome of updating the account
+ * data in the database.
+ */
+enum class AccountReconfigDBResult {
+ /**
+ * This indicates that despite the customer row was
+ * found in the database, its related bank account was not.
+ * This condition is a hard failure of the bank, since
+ * every customer must have one (and only one) bank account.
+ */
+ BANK_ACCOUNT_NOT_FOUND,
+
+ /**
+ * The customer row wasn't found in the database. This error
+ * should be rare, as the client got authenticated in the first
+ * place, before the handler could try the reconfiguration in
+ * the database.
+ */
+ CUSTOMER_NOT_FOUND,
+
+ /**
+ * Reconfiguration successful.
+ */
+ SUCCESS
+} \ 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 9b75f874..8b269b15 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -27,11 +27,6 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
// TOKEN ENDPOINTS
delete("/accounts/{USERNAME}/token") {
val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw unauthorized()
- /**
- * The following command ensures that this call was
- * authenticated with the bearer token and NOT with
- * basic auth. FIXME: this "409 Conflict" case is not documented.
- */
val token = call.getAuthToken() ?: throw badRequest("Basic auth not supported here.")
val resourceName = call.getResourceName("USERNAME")
/**
@@ -327,18 +322,13 @@ fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
if (!accountName.canI(c, withAdmin = true)) throw forbidden()
val req = call.receive<AccountPasswordChange>()
val hashedPassword = CryptoUtil.hashpw(req.new_password)
- /**
- * FIXME: should it check if the password used to authenticate
- * FIXME: this request _is_ the one being overridden in the database?
- */
if (!db.customerChangePassword(
accountName,
hashedPassword
- ))
- throw notFound(
- "Account '$accountName' not found (despite it being authenticated by this call)",
- talerEc = TalerErrorCode.TALER_EC_END // FIXME: need at least GENERIC_NOT_FOUND.
- )
+ )) throw notFound(
+ "Account '$accountName' not found (despite it being authenticated by this call)",
+ talerEc = TalerErrorCode.TALER_EC_END // FIXME: need at least GENERIC_NOT_FOUND.
+ )
call.respond(HttpStatusCode.NoContent)
return@patch
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index e9b9b9a7..dc7eb04b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -398,6 +398,45 @@ class Database(private val dbConfig: String, private val bankCurrency: String) {
}
// MIXED CUSTOMER AND BANK ACCOUNT DATA
+
+ /**
+ * Updates accounts according to the PATCH /accounts/foo endpoint.
+ * The 'login' parameter decides which customer and bank account rows
+ * will get the update.
+ *
+ * The return type expresses either success, or that the target rows
+ * could not be found.
+ */
+ fun accountReconfig(
+ login: String,
+ name: String,
+ cashoutPayto: String,
+ phoneNumber: String,
+ emailAddress: String,
+ isTalerExchange: Boolean
+ ): AccountReconfigDBResult {
+ reconnect()
+ val stmt = prepare("""
+ SELECT
+ out_nx_customer,
+ out_nx_bank_account
+ FROM account_reconfig(?, ?, ?, ?, ?, ?)
+ """)
+ stmt.setString(1, login)
+ stmt.setString(2, name)
+ stmt.setString(3, phoneNumber)
+ stmt.setString(4, emailAddress)
+ stmt.setString(5, cashoutPayto)
+ stmt.setBoolean(6, isTalerExchange)
+ val res = stmt.executeQuery()
+ res.use {
+ if (!it.next()) throw internalServerError("accountReconfig() returned nothing")
+ if (it.getBoolean("out_nx_customer")) return AccountReconfigDBResult.CUSTOMER_NOT_FOUND
+ if (it.getBoolean("out_nx_bank_account")) return AccountReconfigDBResult.BANK_ACCOUNT_NOT_FOUND
+ return AccountReconfigDBResult.SUCCESS
+ }
+ }
+
/**
* Gets the list of public accounts in the system.
* internalCurrency is the bank's currency and loginFilter is
diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt
index 5e56769f..23423eb5 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -26,6 +26,9 @@ import java.time.Instant
import java.util.Random
import java.util.UUID
import kotlin.experimental.inv
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
// Foo pays Bar with custom subject.
fun genTx(
@@ -467,4 +470,63 @@ class DatabaseTest {
// Expecting empty, as the filter should match nothing.
assert(db.accountsGetPublic("KUDOS", "x").isEmpty())
}
+
+ /**
+ * Tests the UPDATE-based SQL function that backs the
+ * PATCH /accounts/foo endpoint.
+ */
+ @Test
+ fun accountReconfigTest() {
+ val db = initDb()
+ // asserting for the customer not being found.
+ db.accountReconfig(
+ "foo",
+ "Foo",
+ "payto://cashout",
+ "+99",
+ "foo@example.com",
+ true
+ ).apply { assertEquals(this, AccountReconfigDBResult.CUSTOMER_NOT_FOUND) }
+ // creating the customer
+ assertNotEquals(db.customerCreate(customerFoo), null)
+
+ // asserting for the bank account not being found.
+ db.accountReconfig(
+ "foo",
+ "Foo",
+ "payto://cashout",
+ "+99",
+ "foo@example.com",
+ true
+ ).apply { assertEquals(this, AccountReconfigDBResult.BANK_ACCOUNT_NOT_FOUND) }
+ // Giving foo a bank account
+ assert(db.bankAccountCreate(bankAccountFoo) != null)
+ // asserting for success.
+ db.accountReconfig(
+ "foo",
+ "Bar",
+ "payto://cashout",
+ "+99",
+ "foo@example.com",
+ true
+ ).apply { assertEquals(this, AccountReconfigDBResult.SUCCESS) }
+ // Getting the updated account from the database and checking values.
+ db.customerGetFromLogin("foo").apply {
+ assertNotEquals(this, null)
+ assert((this!!.login == "foo") &&
+ (this.name == "Bar") &&
+ (this.cashoutPayto) == "payto://cashout" &&
+ (this.email) == "foo@example.com" &&
+ this.phone == "+99"
+ )
+ db.bankAccountGetFromOwnerId(this.expectRowId()).apply {
+ assertNotEquals(this, null)
+ assertTrue(this!!.isTalerExchange)
+ }
+ }
+ }
}
+
+
+
+
diff --git a/database-versioning/procedures.sql b/database-versioning/procedures.sql
index ad36cc2a..802ca7f4 100644
--- a/database-versioning/procedures.sql
+++ b/database-versioning/procedures.sql
@@ -86,6 +86,57 @@ END $$;
COMMENT ON PROCEDURE bank_set_config(TEXT, TEXT)
IS 'Update or insert configuration values';
+CREATE OR REPLACE FUNCTION account_reconfig(
+ IN in_login TEXT,
+ IN in_name TEXT,
+ IN in_phone TEXT,
+ IN in_email TEXT,
+ IN in_cashout_payto TEXT,
+ IN in_is_taler_exchange BOOLEAN,
+ OUT out_nx_customer BOOLEAN,
+ OUT out_nx_bank_account BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+my_customer_id INT8;
+BEGIN
+SELECT
+ customer_id
+ INTO my_customer_id
+ FROM customers
+ WHERE login=in_login;
+IF NOT FOUND
+THEN
+ out_nx_customer=TRUE;
+ RETURN;
+END IF;
+out_nx_customer=FALSE;
+
+UPDATE bank_accounts
+ SET is_taler_exchange = in_is_taler_exchange
+ WHERE owning_customer_id = my_customer_id;
+IF NOT FOUND
+THEN
+ out_nx_bank_account=TRUE;
+ RETURN;
+END IF;
+out_nx_bank_account=FALSE;
+-- bank account patching worked, custom must as well
+-- since this runs in a DB transaction and the customer
+-- was found earlier in this function.
+UPDATE customers
+SET
+ name=in_name,
+ cashout_payto=in_cashout_payto,
+ phone=in_phone,
+ email=in_email
+WHERE customer_id = my_customer_id;
+END $$;
+
+COMMENT ON FUNCTION account_reconfig(TEXT, TEXT, TEXT, TEXT, TEXT, BOOLEAN)
+ IS 'Updates values on customer and bank account rows based on the input data.';
+
CREATE OR REPLACE FUNCTION customer_delete(
IN in_login TEXT,
OUT out_nx_customer BOOLEAN,