diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-10-09 16:18:44 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-10-09 16:18:44 +0200 |
commit | 91d76e7861149af36a8875bbb0811dda4e0485c4 (patch) | |
tree | c8566ab3c7b8ffcfbe215b823fbe273ec93f72ec /src/backenddb | |
parent | d16c03c64da9f58e9bbf6b14bae69da633566da6 (diff) | |
download | merchant-91d76e7861149af36a8875bbb0811dda4e0485c4.tar.gz merchant-91d76e7861149af36a8875bbb0811dda4e0485c4.tar.bz2 merchant-91d76e7861149af36a8875bbb0811dda4e0485c4.zip |
-starting work on /kyc handler
Diffstat (limited to 'src/backenddb')
-rw-r--r-- | src/backenddb/Makefile.am | 4 | ||||
-rw-r--r-- | src/backenddb/drop0001.sql | 12 | ||||
-rw-r--r-- | src/backenddb/drop0002.sql | 32 | ||||
-rw-r--r-- | src/backenddb/merchant-0001.sql | 2 | ||||
-rw-r--r-- | src/backenddb/merchant-0003.sql | 52 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 250 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 152 |
7 files changed, 449 insertions, 55 deletions
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index 300af69f..8adc1d76 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -14,8 +14,8 @@ sql_DATA = \ merchant-0000.sql \ merchant-0001.sql \ merchant-0002.sql \ - drop0001.sql \ - drop0002.sql + merchant-0003.sql \ + drop0001.sql if HAVE_POSTGRESQL if HAVE_GNUNETPQ diff --git a/src/backenddb/drop0001.sql b/src/backenddb/drop0001.sql index aec588da..4a028bab 100644 --- a/src/backenddb/drop0001.sql +++ b/src/backenddb/drop0001.sql @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2014--2020 Taler Systems SA +-- Copyright (C) 2014--2021 Taler Systems SA -- -- TALER is free software; you can redistribute it and/or modify it under the -- terms of the GNU General Public License as published by the Free Software @@ -54,5 +54,15 @@ DROP TABLE IF EXISTS merchant_tip_pickup_signatures CASCADE; -- Unregister patch (0001.sql) SELECT _v.unregister_patch('merchant-0001'); +-- Unregister patch (0002.sql) +SELECT _v.unregister_patch('merchant-0002'); + +DROP TABLE IF EXISTS merchant_kyc CASCADE; + +-- Unregister patch (0003.sql) +SELECT _v.unregister_patch('merchant-0003'); + + + -- And we're out of here... COMMIT; diff --git a/src/backenddb/drop0002.sql b/src/backenddb/drop0002.sql deleted file mode 100644 index a90a4304..00000000 --- a/src/backenddb/drop0002.sql +++ /dev/null @@ -1,32 +0,0 @@ --- --- This file is part of TALER --- Copyright (C) 2021 Taler Systems SA --- --- TALER is free software; you can redistribute it and/or modify it under the --- terms of the GNU General Public License as published by the Free Software --- Foundation; either version 3, or (at your option) any later version. --- --- TALER is distributed in the hope that it will be useful, but WITHOUT ANY --- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR --- A PARTICULAR PURPOSE. See the GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License along with --- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> --- - --- Everything in one big transaction -BEGIN; - --- This script DROPs all of the tables we create, including the --- versioning schema! --- --- Unlike the other SQL files, it SHOULD be updated to reflect the --- latest requirements for dropping tables. - --- Drops for 0002.sql - --- Unregister patch (0002.sql) -SELECT _v.unregister_patch('merchant-0002'); - --- And we're out of here... -COMMIT; diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql index ed728ab6..9e769067 100644 --- a/src/backenddb/merchant-0001.sql +++ b/src/backenddb/merchant-0001.sql @@ -97,7 +97,7 @@ CREATE TABLE IF NOT EXISTS merchant_accounts ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64) - ,salt BYTEA NOT NULL CHECK (LENGTH(salt)=64) + ,salt BYTEA NOT NULL CHECK (LENGTH(salt)=64) -- FIXME: eventually migrate to 16-bit salt value here! #7032 ,payto_uri VARCHAR NOT NULL ,active BOOLEAN NOT NULL ,UNIQUE (merchant_serial,payto_uri) diff --git a/src/backenddb/merchant-0003.sql b/src/backenddb/merchant-0003.sql new file mode 100644 index 00000000..00c77656 --- /dev/null +++ b/src/backenddb/merchant-0003.sql @@ -0,0 +1,52 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2021 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +-- This file includes migrations up to 0.8.5. +-- All migrations after that release should +-- to into a different file. + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('merchant-0003', NULL, NULL); + +CREATE TABLE IF NOT EXISTS merchant_kyc +(kyc_serial_id BIGSERIAL UNIQUE +,kyc_timestamp INT8 NOT NULL +,kyc_ok BOOLEAN NOT NULL DEFAULT (false) +,exchange_kyc_serial INT8 NOT NULL DEFAULT(0) +,account_serial INT8 NOT NULL + REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE +,exchange_url VARCHAR NOT NULL +,PRIMARY KEY (account_serial,exchange_url) +); +COMMENT ON TABLE merchant_kyc + IS 'Status of the KYC process of a merchant account at an exchange'; +COMMENT ON COLUMN merchant_kyc.kyc_timestamp + IS 'Last time we checked our KYC status at the exchange. Useful to re-check if the status is very stale.'; +COMMENT ON COLUMN merchant_kyc.exchange_kyc_serial + IS 'Number to use in the KYC-endpoints of the exchange to check the KYC status or begin the KYC process. 0 if we do not know it yet.'; +COMMENT ON COLUMN merchant_kyc.kyc_ok + IS 'true if the KYC check was passed successfully'; +COMMENT ON COLUMN merchant_kyc.account_serial + IS 'Which bank account of the merchant is the KYC status for'; +COMMENT ON COLUMN merchant_kyc.exchange_url + IS 'Which exchange base URL is this KYC status valid for'; + + +-- Complete transaction +COMMIT; diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 45f02757..838a6897 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -111,7 +111,7 @@ struct PostgresClosure * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ -static int +static enum GNUNET_GenericReturnValue postgres_drop_tables (void *cls) { struct PostgresClosure *pc = cls; @@ -135,7 +135,7 @@ postgres_drop_tables (void *cls) * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ -static int +static enum GNUNET_GenericReturnValue postgres_create_tables (void *cls) { struct PostgresClosure *pc = cls; @@ -261,7 +261,7 @@ check_connection (struct PostgresClosure *pg) * must point to a constant * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue postgres_start (void *cls, const char *name) { @@ -297,7 +297,7 @@ postgres_start (void *cls, * must point to a constant * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue postgres_start_read_committed (void *cls, const char *name) { @@ -821,6 +821,210 @@ postgres_insert_account ( /** + * Closure for kyc_status_cb(). + */ +struct KycStatusContext +{ + /** + * Function to call with results. + */ + TALER_MERCHANTDB_KycCallback kyc_cb; + + /** + * Closure for @e kyc_cb. + */ + void *kyc_cb_cls; + + /** + * Filter, NULL to not filter. + */ + const struct GNUNET_HashCode *h_wire; + + /** + * Filter, NULL to not filter. + */ + const char *exchange_url; + + /** + * Number of results found. + */ + unsigned int count; + + /** + * Set to true on failure(s). + */ + bool failure; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about accounts. + * + * @param[in,out] cls of type `struct KycStatusContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +kyc_status_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct KycStatusContext *ksc = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + struct GNUNET_HashCode h_wire; + uint64_t kyc_serial; + char *exchange_url; + char *payto_uri; + struct GNUNET_TIME_Absolute last_check; + uint8_t kyc_ok; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_wire", + &h_wire), + GNUNET_PQ_result_spec_uint64 ("exchange_kyc_serial", + &kyc_serial), + GNUNET_PQ_result_spec_string ("payto_uri", + &payto_uri), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), + GNUNET_PQ_result_spec_absolute_time ("kyc_timestamp", + &last_check), + GNUNET_PQ_result_spec_auto_from_type ("kyc_ok", + &kyc_ok), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ksc->failure = true; + return; + } + if ( (NULL != ksc->exchange_url) && + (0 != strcmp (ksc->exchange_url, + exchange_url)) ) + { + GNUNET_PQ_cleanup_result (rs); + continue; + } + if ( (NULL != ksc->h_wire) && + (0 != GNUNET_memcmp (ksc->h_wire, + &h_wire)) ) + { + GNUNET_PQ_cleanup_result (rs); + continue; + } + ksc->count++; + ksc->kyc_cb (ksc->kyc_cb_cls, + &h_wire, + kyc_serial, + payto_uri, + exchange_url, + last_check, + 0 != kyc_ok); + GNUNET_PQ_cleanup_result (rs); + } +} + + +/** + * Check an instance's account's KYC status. + * + * @param cls closure + * @param merchant_id merchant backend instance ID + * @param h_wire hash of the wire account to check, + * NULL to check all accounts of the merchant + * @param exchange_url base URL of the exchange to check, + * NULL to check all exchanges + * @param kyc_cb KYC status callback to invoke + * @param kyc_cb_cls closure for @a kyc_cb + * @return database result code + */ +static enum GNUNET_DB_QueryStatus +postgres_account_kyc_get_status (void *cls, + const char *merchant_id, + const struct GNUNET_HashCode *h_wire, + const char *exchange_url, + TALER_MERCHANTDB_KycCallback kyc_cb, + void *kyc_cb_cls) +{ + struct PostgresClosure *pg = cls; + struct KycStatusContext ksc = { + .kyc_cb = kyc_cb, + .kyc_cb_cls = kyc_cb_cls, + .exchange_url = exchange_url, + .h_wire = h_wire + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (merchant_id), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_kyc_status", + params, + &kyc_status_cb, + &ksc); + if (ksc.failure) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (0 > qs) + return qs; + return ksc.count; +} + + +/** + * Update an instance's account's KYC status. + * + * @param cls closure + * @param merchant_id merchant backend instance ID + * @param h_wire hash of the wire account to check + * @param exchange_url base URL of the exchange to check + * @param exchange_kyc_serial serial number for our account at the exchange (0 if unknown) + * @param kyc_ok current KYC status (true for satisfied) + * @return database result code + */ +static enum GNUNET_DB_QueryStatus +postgres_account_kyc_set_status ( + void *cls, + const char *merchant_id, + const struct GNUNET_HashCode *h_wire, + const char *exchange_url, + uint64_t exchange_kyc_serial, + bool kyc_ok) +{ + struct PostgresClosure *pg = cls; + uint8_t ok = kyc_ok; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (merchant_id), + GNUNET_PQ_query_param_auto_from_type (h_wire), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_uint64 (&exchange_kyc_serial), + GNUNET_PQ_query_param_absolute_time (&now), + GNUNET_PQ_query_param_auto_from_type (&ok), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + (void) GNUNET_TIME_round_abs (&now); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "upsert_account_kyc", + params); +} + + +/** * Delete private key of an instance from our database. * * @param cls closure @@ -6637,6 +6841,40 @@ postgres_connect (void *cls) " FROM merchant_instances" " WHERE merchant_id=$2", 2), + /* for postgres_account_kyc_set_status */ + GNUNET_PQ_make_prepare ("upsert_account_kyc", + "INSERT INTO merchant_kyc" + "(kyc_timestamp" + ",kyc_ok" + ",exchange_kyc_serial" + ",account_serial" + ",exchange_url)" + " SELECT $5, $6, $4, account_serial, $3" + " FROM merchant_instances" + " JOIN merchant_accounts USING (merchant_serial)" + " WHERE merchant_id=$1" + " AND h_wire=$2" + " ON CONFLICT(account_serial,exchange_url) DO " + "UPDATE" + " SET exchange_kyc_serial=$4" + " ,kyc_ok=$6", + 6), + /* for postgres_account_kyc_get_status */ + GNUNET_PQ_make_prepare ("lookup_kyc_status", + "SELECT" + " h_wire" + ",exchange_kyc_serial" + ",payto_uri" + ",exchange_url" + ",kyc_timestamp" + ",kyc_ok" + " FROM merchant_instances" + " JOIN merchant_accounts" + " USING (merchant_serial)" + " JOIN merchant_kyc" + " USING (account_serial)" + " WHERE merchant_instances.merchant_id=$1", + 1), /* for postgres_insert_account() */ GNUNET_PQ_make_prepare ("insert_account", "INSERT INTO merchant_accounts" @@ -9091,6 +9329,10 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->lookup_instance_auth = &postgres_lookup_instance_auth; plugin->insert_instance = &postgres_insert_instance; plugin->insert_account = &postgres_insert_account; + plugin->account_kyc_set_status + = &postgres_account_kyc_set_status; + plugin->account_kyc_get_status + = &postgres_account_kyc_get_status; plugin->delete_instance_private_key = &postgres_delete_instance_private_key; plugin->purge_instance = &postgres_purge_instance; plugin->update_instance = &postgres_update_instance; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index 818172d9..9fd159d8 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -37,8 +37,11 @@ static int result; */ static struct TALER_MERCHANTDB_Plugin *plugin; +/** + * @param test 0 on success, non-zero on failure + */ #define TEST_WITH_FAIL_CLAUSE(test, on_fail) \ - if (0 != (test)) \ + if ((test)) \ { \ GNUNET_break (0); \ on_fail \ @@ -53,6 +56,9 @@ static struct TALER_MERCHANTDB_Plugin *plugin; return 1; \ } +/** + * @param __test 0 on success, non-zero on failure + */ #define TEST_RET_ON_FAIL(__test) \ TEST_WITH_FAIL_CLAUSE (__test, \ return 1; \ @@ -145,8 +151,9 @@ make_account (struct TALER_MERCHANTDB_AccountDetails *account) { GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &account->h_wire); - GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, - &account->salt); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &account->salt, + sizeof (account->salt)); account->payto_uri = "payto://x-taler-bank/bank.demo.taler.net/4"; account->active = true; } @@ -249,10 +256,10 @@ static int check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a, const struct TALER_MERCHANTDB_AccountDetails *b) { - if ((0 != GNUNET_CRYPTO_hash_cmp (&a->h_wire, - &b->h_wire)) || - (0 != GNUNET_CRYPTO_hash_cmp (&a->salt, - &b->salt)) || + if ((0 != GNUNET_memcmp (&a->h_wire, + &b->h_wire)) || + (0 != GNUNET_memcmp (&a->salt, + &b->salt)) || (0 != strcmp (a->payto_uri, b->payto_uri)) || (a->active != b->active)) @@ -6647,6 +6654,119 @@ test_lookup_orders_all_filters (void) } +static void +kyc_status_ok (void *cls, + const struct GNUNET_HashCode *h_wire, + uint64_t exchange_kyc_serial, + const char *payto_uri, + const char *exchange_url, + struct GNUNET_TIME_Absolute last_check, + bool kyc_ok) +{ + bool *fail = cls; + + if (kyc_ok) + *fail = false; +} + + +static void +kyc_status_fail (void *cls, + const struct GNUNET_HashCode *h_wire, + uint64_t exchange_kyc_serial, + const char *payto_uri, + const char *exchange_url, + struct GNUNET_TIME_Absolute last_check, + bool kyc_ok) +{ + bool *fail = cls; + + if (! kyc_ok) + *fail = false; +} + + +/** + * Function that tests the KYC table. + * + * @return 0 on success, 1 otherwise. + */ +static int +test_kyc (void) +{ + struct InstanceData instance; + struct TALER_MERCHANTDB_AccountDetails account; + bool fail; + + make_instance ("test_kyc", + &instance); + make_account (&account); + TEST_RET_ON_FAIL (test_insert_instance (&instance, + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + TEST_RET_ON_FAIL (test_insert_account (&instance, + &account, + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->account_kyc_set_status (plugin->cls, + instance.instance.id, + &account.h_wire, + "https://exchange.net/", + 1LLU, + false)); + TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->account_kyc_set_status (plugin->cls, + instance.instance.id, + &account.h_wire, + "https://exchange2.com/", + 1LLU, + false)); + TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->account_kyc_set_status (plugin->cls, + instance.instance.id, + &account.h_wire, + "https://exchange.net/", + 1LLU, + true)); + fail = true; + TEST_RET_ON_FAIL (1 != + plugin->account_kyc_get_status (plugin->cls, + instance.instance.id, + &account.h_wire, + "https://exchange.net/", + &kyc_status_ok, + &fail)); + TEST_RET_ON_FAIL (fail); + fail = true; + TEST_RET_ON_FAIL (1 != + plugin->account_kyc_get_status (plugin->cls, + instance.instance.id, + NULL, + "https://exchange2.com/", + &kyc_status_fail, + &fail)); + TEST_RET_ON_FAIL (fail); + fail = true; + TEST_RET_ON_FAIL (2 != + plugin->account_kyc_get_status (plugin->cls, + instance.instance.id, + NULL, + NULL, + &kyc_status_fail, + &fail)); + TEST_RET_ON_FAIL (fail); + fail = true; + TEST_RET_ON_FAIL (2 != + plugin->account_kyc_get_status (plugin->cls, + instance.instance.id, + NULL, + NULL, + &kyc_status_ok, + &fail)); + TEST_RET_ON_FAIL (fail); + return 0; +} + + /** * Function that runs all tests. * @@ -6663,7 +6783,7 @@ run_tests (void) TEST_RET_ON_FAIL (test_tips ()); TEST_RET_ON_FAIL (test_refunds ()); TEST_RET_ON_FAIL (test_lookup_orders_all_filters ()); - + TEST_RET_ON_FAIL (test_kyc ()); return 0; } @@ -6703,14 +6823,16 @@ run (void *cls) plugin->preflight (plugin->cls); result = run_tests (); - - /* Test dropping tables */ - if (GNUNET_OK != plugin->drop_tables (plugin->cls)) + if (0 == result) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Dropping tables failed\n"); - result = 77; - return; + /* Test dropping tables */ + if (GNUNET_OK != plugin->drop_tables (plugin->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Dropping tables failed\n"); + result = 77; + return; + } } TALER_MERCHANTDB_plugin_unload (plugin); |