From 91d76e7861149af36a8875bbb0811dda4e0485c4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 9 Oct 2021 16:18:44 +0200 Subject: -starting work on /kyc handler --- contrib/merchant-backoffice | 2 +- src/backend/Makefile.am | 2 + src/backend/taler-merchant-httpd.c | 19 +- src/backend/taler-merchant-httpd_get-orders-ID.c | 4 +- src/backend/taler-merchant-httpd_helper.c | 4 +- ...r-merchant-httpd_private-get-instances-ID-kyc.c | 84 +++++++ ...r-merchant-httpd_private-get-instances-ID-kyc.h | 59 +++++ src/backenddb/Makefile.am | 4 +- src/backenddb/drop0001.sql | 12 +- src/backenddb/drop0002.sql | 32 --- src/backenddb/merchant-0001.sql | 2 +- src/backenddb/merchant-0003.sql | 52 +++++ src/backenddb/plugin_merchantdb_postgres.c | 250 ++++++++++++++++++++- src/backenddb/test_merchantdb.c | 152 +++++++++++-- src/include/taler_merchantdb_plugin.h | 99 ++++++-- 15 files changed, 698 insertions(+), 79 deletions(-) create mode 100644 src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c create mode 100644 src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h delete mode 100644 src/backenddb/drop0002.sql create mode 100644 src/backenddb/merchant-0003.sql diff --git a/contrib/merchant-backoffice b/contrib/merchant-backoffice index 182cdfff..824aa7a8 160000 --- a/contrib/merchant-backoffice +++ b/contrib/merchant-backoffice @@ -1 +1 @@ -Subproject commit 182cdfffa1d4b6f2bb3543d30cfa7509e73bda03 +Subproject commit 824aa7a80b4c2e63d23985751f34c9492d396a36 diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index da825d3b..073fff5e 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -48,6 +48,8 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_private-get-instances.h \ taler-merchant-httpd_private-get-instances-ID.c \ taler-merchant-httpd_private-get-instances-ID.h \ + taler-merchant-httpd_private-get-instances-ID-kyc.c \ + taler-merchant-httpd_private-get-instances-ID-kyc.h \ taler-merchant-httpd_private-get-products.c \ taler-merchant-httpd_private-get-products.h \ taler-merchant-httpd_private-get-products-ID.c \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index e031c2ba..feb90b28 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -39,6 +39,7 @@ #include "taler-merchant-httpd_private-delete-transfers-ID.h" #include "taler-merchant-httpd_private-get-instances.h" #include "taler-merchant-httpd_private-get-instances-ID.h" +#include "taler-merchant-httpd_private-get-instances-ID-kyc.h" #include "taler-merchant-httpd_private-get-products.h" #include "taler-merchant-httpd_private-get-products-ID.h" #include "taler-merchant-httpd_private-get-orders.h" @@ -147,7 +148,7 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg; char *TMH_default_auth; -int +enum GNUNET_GenericReturnValue TMH_check_auth (const char *token, const struct GNUNET_ShortHashCode *salt, const struct GNUNET_HashCode *hash) @@ -646,6 +647,16 @@ url_handler (void *cls, /* Body should be pretty small. */ .max_upload = 1024 * 1024 }, + /* POST /kyc: */ + { + .url_prefix = "/instances/", + .url_suffix = "kyc", + .method = MHD_HTTP_METHOD_GET, + .skip_instance = true, + .default_only = true, + .have_id_segment = true, + .handler = &TMH_private_get_instances_default_ID_kyc, + }, { NULL } @@ -685,6 +696,12 @@ url_handler (void *cls, /* Body should be pretty small. */ .max_upload = 1024 * 1024, }, + /* GET /kyc: */ + { + .url_prefix = "/kyc", + .method = MHD_HTTP_METHOD_GET, + .handler = &TMH_private_get_instances_ID_kyc, + }, /* GET /products: */ { .url_prefix = "/products", diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c index 0025a6ce..dbb5ef10 100644 --- a/src/backend/taler-merchant-httpd_get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_get-orders-ID.c @@ -427,7 +427,7 @@ TMH_make_order_status_url (struct MHD_Connection *con, "?token="); GNUNET_buffer_write_data_encoded (&buf, (char *) claim_token, - sizeof (struct TALER_ClaimTokenP)); + sizeof (*claim_token)); num_qp++; } @@ -454,7 +454,7 @@ TMH_make_order_status_url (struct MHD_Connection *con, "?h_contract="); GNUNET_buffer_write_data_encoded (&buf, (char *) h_contract, - sizeof (struct GNUNET_HashCode)); + sizeof (*h_contract)); } return GNUNET_buffer_reap_str (&buf); diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c index 2427b1e0..f4129e0e 100644 --- a/src/backend/taler-merchant-httpd_helper.c +++ b/src/backend/taler-merchant-httpd_helper.c @@ -315,9 +315,7 @@ TMH_taxes_array_valid (const json_t *taxes) struct TMH_WireMethod * TMH_setup_wire_account (const char *payto_uri) { - // FIXME: avoid use of huge 512-bit salt!? - // Seems 128-bit should suffice, right? - struct GNUNET_HashCode salt; + struct TALER_WireSalt salt; struct TMH_WireMethod *wm; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c new file mode 100644 index 00000000..a87b4162 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c @@ -0,0 +1,84 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems SA + + GNU Taler is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + GNU 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 +*/ + +/** + * @file taler-merchant-httpd_private-get-instances-ID-kyc.c + * @brief implementing GET /instances/$ID/kyc request handling + * @kycor Christian Grothoff + * @kycor Florian Dold + */ +#include "platform.h" +#include "taler-merchant-httpd_private-get-instances-ID-kyc.h" +#include "taler-merchant-httpd_helper.h" +#include + + +/** + * Check the KYC status of an instance. + * + * @param mi instance to check KYC status of + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +static MHD_RESULT +get_instances_ID_kyc (struct TMH_MerchantInstance *mi, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + GNUNET_break (0); + return MHD_NO; +} + + +MHD_RESULT +TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + + return get_instances_ID_kyc (mi, + connection, + hc); +} + + +MHD_RESULT +TMH_private_get_instances_default_ID_kyc (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi; + + mi = TMH_lookup_instance (hc->infix); + if (NULL == mi) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, + hc->infix); + } + return get_instances_ID_kyc (mi, + connection, + hc); +} + + +/* end of taler-merchant-httpd_private-get-instances-ID-kyc.c */ diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h new file mode 100644 index 00000000..58762c86 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h @@ -0,0 +1,59 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems SA + + GNU Taler is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation; either version 3, + or (at your option) any later version. + + GNU 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 +*/ + +/** + * @file taler-merchant-httpd_private-get-instances-ID-kyc.h + * @brief implements GET /instances/$ID/kyc request handling + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_KYC_H +#define TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_KYC_H +#include "taler-merchant-httpd.h" + + +/** + * Change the instance's kyc settings. + * This is the handler called using the instance's own kycentication. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + + +/** + * Change the instance's kyc settings. + * This is the handler called using the default instance's kycentication. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_get_instances_default_ID_kyc (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif 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 --- - --- 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 +-- + +-- 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) { @@ -820,6 +820,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. * @@ -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); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 97079680..90681ac6 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -46,7 +46,7 @@ struct TALER_MERCHANTDB_AccountDetails /** * Salt value used for hashing @e payto_uri. */ - struct GNUNET_HashCode salt; + struct TALER_WireSalt salt; /** * Actual account address as a payto://-URI. @@ -381,6 +381,29 @@ typedef void bool pending); +/** + * Function called from ``account_kyc_get_status`` + * with KYC status information for this merchant. + * + * @param cls closure + * @param h_wire hash of the wire account + * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown + * @param payto_uri payto:// URI of the merchant's bank account + * @param exchange_url base URL of the exchange for which this is a status + * @param last_check when did we last get an update on our KYC status from the exchange + * @param kyc_ok true if we satisfied the KYC requirements + */ +typedef void +(*TALER_MERCHANTDB_KycCallback)( + 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); + + /** * Results from trying to increase a refund. */ @@ -695,8 +718,8 @@ struct TALER_MERCHANTDB_Plugin * * @param cls closure */ - int - (*connect) (void *cls); + enum GNUNET_GenericReturnValue + (*connect)(void *cls); /** * Drop merchant tables. Used for testcases and to reset the DB. @@ -704,8 +727,8 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ - int - (*drop_tables) (void *cls); + enum GNUNET_GenericReturnValue + (*drop_tables)(void *cls); /** * Initialize merchant tables @@ -713,8 +736,8 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ - int - (*create_tables) (void *cls); + enum GNUNET_GenericReturnValue + (*create_tables)(void *cls); /** * Register callback to be invoked on events of type @a es. @@ -776,9 +799,9 @@ struct TALER_MERCHANTDB_Plugin * must point to a constant * @return #GNUNET_OK on success */ - int - (*start) (void *cls, - const char *name); + enum GNUNET_GenericReturnValue + (*start)(void *cls, + const char *name); /** * Start a transaction with isolation level 'read committed'. @@ -788,9 +811,9 @@ struct TALER_MERCHANTDB_Plugin * must point to a constant * @return #GNUNET_OK on success */ - int - (*start_read_committed) (void *cls, - const char *name); + enum GNUNET_GenericReturnValue + (*start_read_committed)(void *cls, + const char *name); /** * Roll back the current transaction of a database connection. @@ -928,10 +951,10 @@ struct TALER_MERCHANTDB_Plugin * @return database result code */ enum GNUNET_DB_QueryStatus - (*update_instance_auth)(void *cls, - const char *merchant_id, - const struct - TALER_MERCHANTDB_InstanceAuthSettings *ias); + (*update_instance_auth)( + void *cls, + const char *merchant_id, + const struct TALER_MERCHANTDB_InstanceAuthSettings *ias); /** * Set an instance's account in our database to "inactive". @@ -960,6 +983,48 @@ struct TALER_MERCHANTDB_Plugin const char *merchant_id, const struct GNUNET_HashCode *h_wire); + + /** + * 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 + */ + enum GNUNET_DB_QueryStatus + (*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); + + /** + * 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 + */ + enum GNUNET_DB_QueryStatus + (*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); + + /** * Lookup all of the products the given instance has configured. * -- cgit v1.2.3