diff options
author | MS <ms@taler.net> | 2023-10-04 13:35:34 +0200 |
---|---|---|
committer | MS <ms@taler.net> | 2023-10-04 13:38:29 +0200 |
commit | f022b73168432b7cb425a5a69f41b616ce34829f (patch) | |
tree | 2b22e6d08667ee4eb5a070bfafed1aec548471f6 | |
parent | 377b22e474af8188b5a1e53e68c43f1920b3bcc9 (diff) | |
download | libeufin-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.kt | 29 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt | 18 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Database.kt | 39 | ||||
-rw-r--r-- | bank/src/test/kotlin/DatabaseTest.kt | 62 | ||||
-rw-r--r-- | database-versioning/procedures.sql | 51 |
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, |