diff options
author | Antoine A <> | 2023-12-06 13:28:56 +0000 |
---|---|---|
committer | Antoine A <> | 2023-12-06 13:28:56 +0000 |
commit | ca8914cd6342babaaceec672fad2f076869b8201 (patch) | |
tree | f9f390a6b91ab405547475f4f0858fbf60de0cbe | |
parent | c12426e1e485038637fa46163f0dacf97c3b59a1 (diff) | |
download | libeufin-ca8914cd6342babaaceec672fad2f076869b8201.tar.gz libeufin-ca8914cd6342babaaceec672fad2f076869b8201.tar.bz2 libeufin-ca8914cd6342babaaceec672fad2f076869b8201.zip |
Improve accounts API
-rw-r--r-- | API_CHANGES.md | 6 | ||||
-rw-r--r-- | bank/conf/test.conf | 2 | ||||
-rw-r--r-- | bank/conf/test_no_tan.conf | 1 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Config.kt | 4 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Constants.kt | 2 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 36 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 34 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 34 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 41 | ||||
-rw-r--r-- | bank/src/test/kotlin/CoreBankApiTest.kt | 71 | ||||
-rw-r--r-- | bank/src/test/kotlin/helpers.kt | 2 | ||||
-rw-r--r-- | contrib/bank.conf | 6 | ||||
-rw-r--r-- | database-versioning/libeufin-bank-procedures.sql | 16 | ||||
-rw-r--r-- | integration/test/IntegrationTest.kt | 2 | ||||
-rw-r--r-- | util/src/main/kotlin/TalerErrorCode.kt | 8 |
15 files changed, 175 insertions, 90 deletions
diff --git a/API_CHANGES.md b/API_CHANGES.md index 8e4c9735..8a83c38d 100644 --- a/API_CHANGES.md +++ b/API_CHANGES.md @@ -8,6 +8,12 @@ This files contains all the API changes for the current release: - CREATE /accounts: new debit_threshold field similar to the one of PATH /accounts - GET /config: new default_debit_threshold field for the default debt limit for newly created accounts - GET /config: new supported_tan_channels field which lists all the TAN channels supported by the server +- GET /config: new allow_edit_name and allow_edit_cashout_payto_uri fields for path authorisation +- POST /accounts: rename challenge_contact_data to contact_data and internal_payto_uri to payto_uri +- PATCH /accounts/USERNAME: add is_public, remove is_taler_exchange and rename challenge_contact_data to contact_data +- GET /accounts: add payto_uri, is_public and is_taler_exchange +- GET /accounts/USERNAME: add is_public and is_taler_exchange +- GET /public-accounts: add is_taler_exchange and rename account_name to username ## bank cli diff --git a/bank/conf/test.conf b/bank/conf/test.conf index b5f9e93f..b4eb3953 100644 --- a/bank/conf/test.conf +++ b/bank/conf/test.conf @@ -3,6 +3,8 @@ DEFAULT_DEBT_LIMIT = KUDOS:100 SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com ALLOW_REGISTRATION = yes ALLOW_ACCOUNT_DELETION = yes +ALLOW_EDIT_NAME = yes +ALLOW_EDIT_CASHOUT_PAYTO_URI = yes allow_conversion = YES FIAT_CURRENCY = EUR tan_sms = libeufin-tan-file.sh diff --git a/bank/conf/test_no_tan.conf b/bank/conf/test_no_tan.conf index 620cdbe4..52e824b2 100644 --- a/bank/conf/test_no_tan.conf +++ b/bank/conf/test_no_tan.conf @@ -5,6 +5,7 @@ allow_conversion = YES FIAT_CURRENCY = EUR ALLOW_REGISTRATION = yes ALLOW_ACCOUNT_DELETION = yes +ALLOW_EDIT_CASHOUT_PAYTO_URI = yes [libeufin-bankdb-postgres] CONFIG = postgresql:///libeufincheck diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt index 936944d4..c3f7db94 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -37,6 +37,8 @@ data class BankConfig( val regionalCurrencySpec: CurrencySpecification, val allowRegistration: Boolean, val allowAccountDeletion: Boolean, + val allowEditName: Boolean, + val allowEditCashout: Boolean, val defaultDebtLimit: TalerAmount, val registrationBonus: TalerAmount, val suggestedWithdrawalExchange: String?, @@ -115,6 +117,8 @@ fun TalerConfig.loadBankConfig(): BankConfig { regionalCurrencySpec = currencySpecificationFor(regionalCurrency), allowRegistration = lookupBoolean("libeufin-bank", "allow_registration") ?: false, allowAccountDeletion = lookupBoolean("libeufin-bank", "allow_account_deletion") ?: false, + allowEditName = lookupBoolean("libeufin-bank", "allow_edit_name") ?: false, + allowEditCashout = lookupBoolean("libeufin-bank", "allow_edit_cashout_payto_uri") ?: false, defaultDebtLimit = amount("libeufin-bank", "default_debt_limit", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), registrationBonus = amount("libeufin-bank", "registration_bonus", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), suggestedWithdrawalExchange = lookupString("libeufin-bank", "suggested_withdrawal_exchange"), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt index 385c9be0..b475aacd 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt @@ -39,7 +39,7 @@ val RESERVED_ACCOUNTS = setOf("admin", "bank") const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB // API version -const val COREBANK_API_VERSION: String = "0:0:1" +const val COREBANK_API_VERSION: String = "2:0:2" const val CONVERSION_API_VERSION: String = "0:0:0" const val INTEGRATION_API_VERSION: String = "1:0:1" const val WIRE_GATEWAY_API_VERSION: String = "0:0:0"
\ No newline at end of file diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt index 560ab0d2..938ab566 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -54,7 +54,9 @@ fun Routing.coreBankApi(db: Database, ctx: BankConfig) { allow_registrations = ctx.allowRegistration, allow_deletions = ctx.allowAccountDeletion, default_debit_threshold = ctx.defaultDebtLimit, - supported_tan_channels = ctx.tanChannels.keys + supported_tan_channels = ctx.tanChannels.keys, + allow_edit_name = ctx.allowEditName, + allow_edit_cashout_payto_uri = ctx.allowEditCashout ) ) } @@ -152,12 +154,13 @@ suspend fun createAccount(db: Database, ctx: BankConfig, req: RegisterAccountReq ) - val internalPayto = req.internal_payto_uri ?: IbanPayTo(genIbanPaytoUri()) + val internalPayto = req.payto_uri ?: req.internal_payto_uri ?: IbanPayTo(genIbanPaytoUri()) + val contactData = req.contact_data ?: req.challenge_contact_data val res = db.account.create( login = req.username, name = req.name, - email = req.challenge_contact_data?.email, - phone = req.challenge_contact_data?.phone, + email = contactData?.email, + phone = contactData?.phone, cashoutPayto = req.cashout_payto_uri, password = req.password, internalPaytoUri = internalPayto, @@ -173,22 +176,19 @@ suspend fun createAccount(db: Database, ctx: BankConfig, req: RegisterAccountReq suspend fun patchAccount(db: Database, ctx: BankConfig, req: AccountReconfiguration, username: String, isAdmin: Boolean): AccountPatchResult { req.debit_threshold?.run { ctx.checkRegionalCurrency(this) } + val contactData = req.contact_data ?: req.challenge_contact_data - if ((req.is_taler_exchange ?: false) == true && username == "admin") - throw conflict( - "admin account cannot be an exchange", - TalerErrorCode.BANK_PATCH_ADMIN_EXCHANGE - ) - - return db.account.reconfig( + return db.account.reconfig( login = username, name = req.name, cashoutPayto = req.cashout_payto_uri, - emailAddress = req.challenge_contact_data?.email, - isTalerExchange = req.is_taler_exchange, - phoneNumber = req.challenge_contact_data?.phone, + emailAddress = contactData?.email, + isPublic = req.is_public, + phoneNumber = contactData?.phone, debtLimit = req.debit_threshold, - isAdmin = isAdmin + isAdmin = isAdmin, + allowEditName = ctx.allowEditName, + allowEditCashout = ctx.allowEditCashout ) } @@ -255,10 +255,14 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { "Account '$username' not found", TalerErrorCode.BANK_UNKNOWN_ACCOUNT ) - AccountPatchResult.NonAdminLegalName -> throw conflict( + AccountPatchResult.NonAdminName -> throw conflict( "non-admin user cannot change their legal name", TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME ) + AccountPatchResult.NonAdminCashout -> throw conflict( + "non-admin user cannot change their cashout account", + TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT + ) AccountPatchResult.NonAdminDebtLimit -> throw conflict( "non-admin user cannot change their debt limit", TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt index 715a46db..b9af5aeb 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -347,7 +347,11 @@ class EditAccount : CliktCommand( help = "Legal name of the account owner" ) private val exchange: Boolean? by option( - help = "Is this account a taler exchange" + hidden = true + ).boolean() + private val is_public: Boolean? by option( + "--public", + help = "Make this account visible to anyone" ).boolean() private val email: String? by option(help = "E-Mail address used for TAN transmission") private val phone: String? by option(help = "Phone number used for TAN transmission") @@ -363,7 +367,8 @@ class EditAccount : CliktCommand( val req = AccountReconfiguration( name = name, is_taler_exchange = exchange, - challenge_contact_data = ChallengeContactData( + is_public = is_public, + contact_data = ChallengeContactData( email = email, phone = phone, ), @@ -375,10 +380,11 @@ class EditAccount : CliktCommand( logger.info("Account '$username' edited") AccountPatchResult.UnknownAccount -> throw Exception("Account '$username' not found") - AccountPatchResult.NonAdminLegalName -> - throw Exception("non-admin user cannot change their legal name") - AccountPatchResult.NonAdminDebtLimit -> - throw Exception("non-admin user cannot change their debt limit") + AccountPatchResult.NonAdminName, + AccountPatchResult.NonAdminCashout, + AccountPatchResult.NonAdminDebtLimit -> { + // Unreachable as we edit account as admin + } } } } @@ -405,9 +411,16 @@ class CreateAccountOption: OptionGroup() { ).flag() val email: String? by option(help = "E-Mail address used for TAN transmission") val phone: String? by option(help = "Phone number used for TAN transmission") - val cashout_payto_uri: IbanPayTo? by option(help = "Payto URI of a fiant account who receive cashout amount").convert { IbanPayTo(it) } - val internal_payto_uri: IbanPayTo? by option(help = "Payto URI of this account").convert { IbanPayTo(it) } - val debit_threshold: TalerAmount? by option(help = "Max debit allowed for this account").convert { TalerAmount(it) } + val cashout_payto_uri: IbanPayTo? by option( + help = "Payto URI of a fiant account who receive cashout amount" + ).convert { IbanPayTo(it) } + val internal_payto_uri: IbanPayTo? by option(hidden = true).convert { IbanPayTo(it) } + val payto_uri: IbanPayTo? by option( + help = "Payto URI of this account" + ).convert { IbanPayTo(it) } + val debit_threshold: TalerAmount? by option( + help = "Max debit allowed for this account") + .convert { TalerAmount(it) } } class CreateAccount : CliktCommand( @@ -431,12 +444,13 @@ class CreateAccount : CliktCommand( name = name, is_public = is_public, is_taler_exchange = exchange, - challenge_contact_data = ChallengeContactData( + contact_data = ChallengeContactData( email = email, phone = phone, ), cashout_payto_uri = cashout_payto_uri, internal_payto_uri = internal_payto_uri, + payto_uri = payto_uri, debit_threshold = debit_threshold ) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt index 3b7cba75..f3beb697 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -113,12 +113,13 @@ data class RegisterAccountRequest( val name: String, val is_public: Boolean = false, val is_taler_exchange: Boolean = false, - val challenge_contact_data: ChallengeContactData? = null, - // Fiat bank account where to send cashout amounts. + val contact_data: ChallengeContactData? = null, val cashout_payto_uri: IbanPayTo? = null, - // Bank account internal to Libeufin-Bank. + val payto_uri: IbanPayTo? = null, + val debit_threshold: TalerAmount? = null, + // TODO remove val internal_payto_uri: IbanPayTo? = null, - val debit_threshold: TalerAmount? = null + val challenge_contact_data: ChallengeContactData? = null, ) @Serializable @@ -131,11 +132,14 @@ data class RegisterAccountResponse( */ @Serializable data class AccountReconfiguration( - val challenge_contact_data: ChallengeContactData?, - val cashout_payto_uri: IbanPayTo?, - val name: String?, - val is_taler_exchange: Boolean?, - val debit_threshold: TalerAmount? + val contact_data: ChallengeContactData? = null, + val cashout_payto_uri: IbanPayTo? = null, + val name: String? = null, + val is_public: Boolean? = null, + val debit_threshold: TalerAmount? = null, + // TODO remove + val challenge_contact_data: ChallengeContactData? = null, + val is_taler_exchange: Boolean? = null, ) /** @@ -231,6 +235,8 @@ data class Config( val allow_conversion: Boolean, val allow_registrations: Boolean, val allow_deletions: Boolean, + val allow_edit_name: Boolean, + val allow_edit_cashout_payto_uri: Boolean, val default_debit_threshold: TalerAmount, val supported_tan_channels: Set<TanChannel> ) { @@ -276,8 +282,11 @@ data class Balance( data class AccountMinimalData( val username: String, val name: String, + val payto_uri: String, val balance: Balance, - val debit_threshold: TalerAmount + val debit_threshold: TalerAmount, + val is_public: Boolean, + val is_taler_exchange: Boolean ) /** @@ -299,6 +308,8 @@ data class AccountData( val debit_threshold: TalerAmount, val contact_data: ChallengeContactData? = null, val cashout_payto_uri: String? = null, + val is_public: Boolean, + val is_taler_exchange: Boolean ) @Serializable @@ -586,8 +597,11 @@ data class PublicAccountsResponse( */ @Serializable data class PublicAccount( + val username: String, val payto_uri: String, val balance: Balance, + val is_taler_exchange: Boolean, + // TODO remove val account_name: String ) 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 2cf85520..9520512d 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -196,7 +196,8 @@ class AccountDAO(private val db: Database) { /** Result status of customer account patch */ enum class AccountPatchResult { UnknownAccount, - NonAdminLegalName, + NonAdminName, + NonAdminCashout, NonAdminDebtLimit, Success } @@ -208,25 +209,29 @@ class AccountDAO(private val db: Database) { cashoutPayto: IbanPayTo?, phoneNumber: String?, emailAddress: String?, - isTalerExchange: Boolean?, + isPublic: Boolean?, debtLimit: TalerAmount?, - isAdmin: Boolean + isAdmin: Boolean, + allowEditName: Boolean, + allowEditCashout: Boolean, ): AccountPatchResult = db.serializable { conn -> + println("$name $cashoutPayto $allowEditName $allowEditCashout") val stmt = conn.prepareStatement(""" SELECT out_not_found, - out_legal_name_change, + out_name_change, + out_cashout_change, out_debt_limit_change - FROM account_reconfig(?, ?, ?, ?, ?, ?, (?, ?)::taler_amount, ?) + FROM account_reconfig(?, ?, ?, ?, ?, ?, (?, ?)::taler_amount, ?, ?, ?) """) stmt.setString(1, login) stmt.setString(2, name) stmt.setString(3, phoneNumber) stmt.setString(4, emailAddress) stmt.setString(5, cashoutPayto?.canonical) - if (isTalerExchange == null) + if (isPublic == null) stmt.setNull(6, Types.NULL) - else stmt.setBoolean(6, isTalerExchange) + else stmt.setBoolean(6, isPublic) if (debtLimit == null) { stmt.setNull(7, Types.NULL) stmt.setNull(8, Types.NULL) @@ -235,11 +240,14 @@ class AccountDAO(private val db: Database) { stmt.setInt(8, debtLimit.frac) } stmt.setBoolean(9, isAdmin) + stmt.setBoolean(10, allowEditName) + stmt.setBoolean(11, allowEditCashout) stmt.executeQuery().use { when { !it.next() -> throw internalServerError("accountReconfig() returned nothing") it.getBoolean("out_not_found") -> AccountPatchResult.UnknownAccount - it.getBoolean("out_legal_name_change") -> AccountPatchResult.NonAdminLegalName + it.getBoolean("out_name_change") -> AccountPatchResult.NonAdminName + it.getBoolean("out_cashout_change") -> AccountPatchResult.NonAdminCashout it.getBoolean("out_debt_limit_change") -> AccountPatchResult.NonAdminDebtLimit else -> AccountPatchResult.Success } @@ -337,6 +345,8 @@ class AccountDAO(private val db: Database) { ,has_debt ,(max_debt).val AS max_debt_val ,(max_debt).frac AS max_debt_frac + ,is_public + ,is_taler_exchange FROM customers JOIN bank_accounts ON customer_id=owning_customer_id @@ -362,6 +372,8 @@ class AccountDAO(private val db: Database) { } ), debit_threshold = it.getAmount("max_debt", db.bankCurrency), + is_public = it.getBoolean("is_public"), + is_taler_exchange = it.getBoolean("is_taler_exchange") ) } } @@ -377,7 +389,8 @@ class AccountDAO(private val db: Database) { (balance).frac AS balance_frac, has_debt, internal_payto_uri, - c.login + c.login + ,is_taler_exchange FROM bank_accounts JOIN customers AS c ON owning_customer_id = c.customer_id WHERE is_public=true AND c.login LIKE ? AND @@ -388,6 +401,7 @@ class AccountDAO(private val db: Database) { } ) { PublicAccount( + username = it.getString("login"), account_name = it.getString("login"), payto_uri = it.getString("internal_payto_uri"), balance = Balance( @@ -397,7 +411,8 @@ class AccountDAO(private val db: Database) { } else { CreditDebitInfo.credit } - ) + ), + is_taler_exchange = it.getBoolean("is_taler_exchange") ) } @@ -415,6 +430,9 @@ class AccountDAO(private val db: Database) { (b).has_debt AS balance_has_debt, (max_debt).val as max_debt_val, (max_debt).frac as max_debt_frac + ,is_public + ,is_taler_exchange + ,internal_payto_uri FROM customers JOIN bank_accounts AS b ON customer_id = b.owning_customer_id WHERE name LIKE ? AND @@ -436,6 +454,9 @@ class AccountDAO(private val db: Database) { } ), debit_threshold = it.getAmount("max_debt", db.bankCurrency), + is_public = it.getBoolean("is_public"), + is_taler_exchange = it.getBoolean("is_taler_exchange"), + payto_uri = it.getString("internal_payto_uri"), ) } }
\ No newline at end of file diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt index dedca301..f032a6d6 100644 --- a/bank/src/test/kotlin/CoreBankApiTest.kt +++ b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -368,6 +368,18 @@ class CoreBankAccountsApiTest { client.deleteA("/accounts/exchange").assertNoContent() } + suspend fun ApplicationTestBuilder.checkAdminOnly(req: JsonElement, error: TalerErrorCode) { + // Checking ordinary user doesn't get to patch + client.patchA("/accounts/merchant") { + json(req) + }.assertConflict(error) + // Finally checking that admin does get to patch + client.patch("/accounts/merchant") { + pwAuth("admin") + json(req) + }.assertNoContent() + } + // PATCH /accounts/USERNAME @Test fun reconfig() = bankSetup { _ -> @@ -377,11 +389,12 @@ class CoreBankAccountsApiTest { val cashout = IbanPayTo(genIbanPaytoUri()) val req = obj { "cashout_payto_uri" to cashout.canonical - "challenge_contact_data" to obj { + "contact_data" to obj { "phone" to "+99" "email" to "foo@example.com" } - "is_taler_exchange" to true + "name" to "Roger" + "is_public" to true } client.patchA("/accounts/merchant") { json(req) @@ -390,36 +403,11 @@ class CoreBankAccountsApiTest { client.patchA("/accounts/merchant") { json(req) }.assertNoContent() - - suspend fun checkAdminOnly(req: JsonElement, error: TalerErrorCode) { - // Checking ordinary user doesn't get to patch - client.patchA("/accounts/merchant") { - json(req) - }.assertConflict(error) - // Finally checking that admin does get to patch - client.patch("/accounts/merchant") { - pwAuth("admin") - json(req) - }.assertNoContent() - } - - checkAdminOnly( - obj(req) { "name" to "Another Foo" }, - TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME - ) + checkAdminOnly( obj(req) { "debit_threshold" to "KUDOS:100" }, TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT ) - - // Check admin account cannot be exchange - client.patchA("/accounts/admin") { - json { "is_taler_exchange" to true } - }.assertConflict(TalerErrorCode.BANK_PATCH_ADMIN_EXCHANGE) - // But we can change its debt limit - client.patchA("/accounts/admin") { - json { "debit_threshold" to "KUDOS:100" } - }.assertNoContent() // Check currency client.patch("/accounts/merchant") { @@ -428,17 +416,30 @@ class CoreBankAccountsApiTest { }.assertBadRequest() // Check patch - client.get("/accounts/merchant") { - pwAuth("admin") - }.assertOkJson<AccountData> { obj -> - assertEquals("Another Foo", obj.name) + client.getA("/accounts/merchant").assertOkJson<AccountData> { obj -> + assertEquals("Roger", obj.name) assertEquals(cashout.canonical, obj.cashout_payto_uri) assertEquals("+99", obj.contact_data?.phone) assertEquals("foo@example.com", obj.contact_data?.email) assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold) + assert(obj.is_public) + assert(!obj.is_taler_exchange) } } + // Test admin-only account patch + @Test + fun patchRestricted() = bankSetup(conf = "test_restrict.conf") { _ -> + checkAdminOnly( + obj { "name" to "Another Foo" }, + TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME + ) + checkAdminOnly( + obj { "cashout_payto_uri" to genIbanPaytoUri() }, + TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT + ) + } + // PATCH /accounts/USERNAME/auth @Test fun passwordChange() = bankSetup { _ -> @@ -936,7 +937,7 @@ class CoreBankCashoutApiTest { }.assertConflict(TalerErrorCode.BANK_MISSING_TAN_INFO) client.patchA("/accounts/customer") { json { - "challenge_contact_data" to obj { + "contact_data" to obj { "phone" to "+99" "email" to "foo@example.com" } @@ -1007,7 +1008,7 @@ class CoreBankCashoutApiTest { // POST /accounts/{USERNAME}/cashouts @Test - fun create_no_tan() = bankSetup("test_no_tan.conf") { _ -> + fun createNoTan() = bankSetup("test_no_tan.conf") { _ -> val req = obj { "request_uid" to randShortHashCode() "amount_debit" to "KUDOS:1" @@ -1093,7 +1094,7 @@ class CoreBankCashoutApiTest { client.patchA("/accounts/customer") { json { - "challenge_contact_data" to obj { + "contact_data" to obj { "phone" to "+99" } } diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt index 00830bb6..cc02e67d 100644 --- a/bank/src/test/kotlin/helpers.kt +++ b/bank/src/test/kotlin/helpers.kt @@ -236,7 +236,7 @@ suspend fun ApplicationTestBuilder.fillCashoutInfo(account: String) { client.patchA("/accounts/$account") { json { "cashout_payto_uri" to unknownPayto - "challenge_contact_data" to obj { + "contact_data" to obj { "phone" to "+99" } } diff --git a/contrib/bank.conf b/contrib/bank.conf index 5b0ef359..7fe0c853 100644 --- a/contrib/bank.conf +++ b/contrib/bank.conf @@ -15,6 +15,12 @@ CURRENCY = KUDOS # Allow an account to delete itself # ALLOW_ACCOUNT_DELETION = no +# Allow accounts to edit their name +# ALLOW_EDIT_NAME = no + +# Allow accounts to edit their cashout account +# ALLOW_EDIT_CASHOUT_PAYTO_URI = no + # Enable regional currency conversion # ALLOW_CONVERSION = no diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql index d9ed9f18..455d09b2 100644 --- a/database-versioning/libeufin-bank-procedures.sql +++ b/database-versioning/libeufin-bank-procedures.sql @@ -123,11 +123,14 @@ CREATE OR REPLACE FUNCTION account_reconfig( IN in_phone TEXT, IN in_email TEXT, IN in_cashout_payto TEXT, - IN in_is_taler_exchange BOOLEAN, + IN in_is_public BOOLEAN, IN in_max_debt taler_amount, IN in_is_admin BOOLEAN, + IN in_allow_name BOOLEAN, + IN in_allow_cashout BOOLEAN, OUT out_not_found BOOLEAN, - OUT out_legal_name_change BOOLEAN, + OUT out_name_change BOOLEAN, + OUT out_cashout_change BOOLEAN, OUT out_debt_limit_change BOOLEAN ) LANGUAGE plpgsql AS $$ @@ -140,9 +143,10 @@ END IF; -- Get user ID and check reconfig rights SELECT customer_id, - in_name IS NOT NULL AND name != in_name AND NOT in_is_admin, + in_name IS NOT NULL AND name != in_name AND NOT in_is_admin AND NOT in_allow_name, + cashout_payto IS DISTINCT FROM in_cashout_payto AND NOT in_is_admin AND NOT in_allow_cashout, in_max_debt IS NOT NULL AND max_debt != in_max_debt AND NOT in_is_admin - INTO my_customer_id, out_legal_name_change, out_debt_limit_change + INTO my_customer_id, out_name_change, out_cashout_change, out_debt_limit_change FROM customers JOIN bank_accounts ON customer_id=owning_customer_id @@ -150,13 +154,13 @@ SELECT IF NOT FOUND THEN out_not_found=TRUE; RETURN; -ELSIF out_legal_name_change OR out_debt_limit_change THEN +ELSIF out_name_change OR out_cashout_change OR out_debt_limit_change THEN RETURN; END IF; -- Update bank info UPDATE bank_accounts SET - is_taler_exchange = COALESCE(in_is_taler_exchange, is_taler_exchange), + is_public = COALESCE(in_is_public, is_public), max_debt = COALESCE(in_max_debt, max_debt) WHERE owning_customer_id = my_customer_id; -- Update customer info diff --git a/integration/test/IntegrationTest.kt b/integration/test/IntegrationTest.kt index 5820a205..f32d2a75 100644 --- a/integration/test/IntegrationTest.kt +++ b/integration/test/IntegrationTest.kt @@ -106,7 +106,7 @@ class IntegrationTest { "internal_payto_uri" to userPayTo "cashout_payto_uri" to fiatPayTo "debit_threshold" to "KUDOS:100" - "challenge_contact_data" to obj { + "contact_data" to obj { "phone" to "+99" } } diff --git a/util/src/main/kotlin/TalerErrorCode.kt b/util/src/main/kotlin/TalerErrorCode.kt index 70f08e34..ff9dce29 100644 --- a/util/src/main/kotlin/TalerErrorCode.kt +++ b/util/src/main/kotlin/TalerErrorCode.kt @@ -3474,6 +3474,14 @@ enum class TalerErrorCode(val code: Int) { /** + * A non-admin user has tried to change their cashout account. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_NON_ADMIN_PATCH_CASHOUT(5140), + + + /** * The sync service failed find the account in its database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). |