diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-09-07 23:21:17 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-09-07 23:21:17 +0200 |
commit | 23812272e6445aed81c4caefb59053ff0b597431 (patch) | |
tree | eb307244b67f9336004c8686eaf4e7df37fb1fcd | |
parent | 4131cf69ce62dc3b79c7d27d992a9a5f8d934c1c (diff) |
work towards taler-merchant-kyccheck helper
25 files changed, 962 insertions, 320 deletions
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c index ff90c505..3b725596 100644 --- a/src/backend/taler-merchant-depositcheck.c +++ b/src/backend/taler-merchant-depositcheck.c @@ -463,17 +463,17 @@ deposit_get_cb ( dr->details.accepted.kyc_ok, TALER_B2S (&w->coin_pub)); now = GNUNET_TIME_timestamp_get (); + /* FIXME: probably should NOT clobber limits, etc, and + ONLY set kyc_ok (always to false?) */ qs = db_plugin->account_kyc_set_status ( db_plugin->cls, w->instance_id, &w->h_wire, exchange_url, - dr->details.accepted.requirement_row, now, MHD_HTTP_ACCEPTED, TALER_EC_NONE, NULL, - 0, NULL, false, dr->details.accepted.kyc_ok); diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index a121251e..e46a2db5 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -2011,15 +2011,18 @@ url_handler (void *cls, * Callback invoked with information about a bank account. * * @param cls closure with a `struct TMH_MerchantInstance *` + * @param merchant_priv private key of the merchant instance * @param acc details about the account */ static void add_account_cb (void *cls, + const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_AccountDetails *acc) { struct TMH_MerchantInstance *mi = cls; struct TMH_WireMethod *wm; + (void) merchant_priv; wm = GNUNET_new (struct TMH_WireMethod); wm->h_wire = acc->h_wire; wm->payto_uri = GNUNET_strdup (acc->payto_uri); diff --git a/src/backend/taler-merchant-httpd_private-get-accounts.c b/src/backend/taler-merchant-httpd_private-get-accounts.c index e420a0e8..a5791960 100644 --- a/src/backend/taler-merchant-httpd_private-get-accounts.c +++ b/src/backend/taler-merchant-httpd_private-get-accounts.c @@ -26,14 +26,17 @@ * Add account details to our JSON array. * * @param cls a `json_t *` JSON array to build + * @param merchant_priv private key of the merchant instance * @param ad details about the account */ static void add_account (void *cls, + const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_AccountDetails *ad) { json_t *pa = cls; + (void) merchant_priv; GNUNET_assert (0 == json_array_append_new ( pa, 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 index f8bad978..69e962d0 100644 --- a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c +++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c @@ -102,11 +102,6 @@ struct ExchangeKycRequest struct TALER_EXCHANGE_KycCheckHandle *kyc; /** - * KYC number used by the exchange. - */ - uint64_t exchange_kyc_serial; - - /** * Our account's payto URI. */ char *payto_uri; @@ -449,6 +444,34 @@ handle_kyc_timeout (void *cls) /** + * Pack the given @a limit into the JSON @a limits array. + * + * @param limit account limit to pack + * @param[in,out] limits JSON array to extend + */ +static void +pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit, + json_t *limits) +{ + json_t *jl; + + jl = GNUNET_JSON_PACK ( + TALER_JSON_pack_kycte ("operation_type", + limit->operation_type), + GNUNET_JSON_pack_time_rel ("timeframe", + limit->timeframe), + TALER_JSON_pack_amount ("threshold", + &limit->threshold), + GNUNET_JSON_pack_bool ("soft_limit", + limit->soft_limit) + ); + GNUNET_assert (0 == + json_array_append_new (limits, + jl)); +} + + +/** * We are done with the KYC request @a ekr. Remove it from the work list and * check if we are done overall. * @@ -512,92 +535,44 @@ store_kyc_status ( const struct TALER_EXCHANGE_KycStatus *ks, const struct TALER_EXCHANGE_AccountKycStatus *account_kyc_status) { - unsigned int dl_cnt = 0; + json_t *jlimits; + enum GNUNET_DB_QueryStatus qs; + jlimits = json_array (); + GNUNET_assert (NULL != jlimits); for (unsigned int i = 0; i<account_kyc_status->limits_length; i++) { const struct TALER_EXCHANGE_AccountLimit *limit = &account_kyc_status->limits[i]; - if (TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT == - limit->operation_type) - dl_cnt++; + pack_limit (limit, + jlimits); } + qs = TMH_db->account_kyc_set_status ( + TMH_db->cls, + ekr->kc->mi->settings.id, + &ekr->h_wire, + ekr->exchange_url, + GNUNET_TIME_timestamp_get (), + ks->hr.http_status, + ks->hr.ec, + &account_kyc_status->access_token, + jlimits, + account_kyc_status->aml_review, + false); + json_decref (jlimits); + if (qs < 0) { - struct TALER_MERCHANTDB_DepositLimits dls[GNUNET_NZL (dl_cnt)]; - enum GNUNET_DB_QueryStatus qs; - unsigned int off = 0; - - for (unsigned int i = 0; i<account_kyc_status->limits_length; i++) - { - const struct TALER_EXCHANGE_AccountLimit *limit - = &account_kyc_status->limits[i]; - struct TALER_MERCHANTDB_DepositLimits *dl - = &dls[off]; - - if (TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT != - limit->operation_type) - continue; - dl->timeframe = limit->timeframe; - dl->threshold = limit->threshold; - dl->soft_limit = limit->soft_limit; - off++; - } - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - ekr->kc->mi->settings.id, - &ekr->h_wire, - ekr->exchange_url, - ekr->exchange_kyc_serial, - GNUNET_TIME_timestamp_get (), - ks->hr.http_status, - ks->hr.ec, - &account_kyc_status->access_token, - dl_cnt, - dls, - account_kyc_status->aml_review, - false); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to store KYC status in database!\n"); - return false; - } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to store KYC status in database!\n"); + return false; } return true; } /** - * Pack the given @a limit into the JSON @a limits array. - * - * @param limit account limit to pack - * @param[in,out] limits JSON array to extend - */ -static void -pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit, - json_t *limits) -{ - json_t *jl; - - jl = GNUNET_JSON_PACK ( - TALER_JSON_pack_kycte ("operation_type", - limit->operation_type), - GNUNET_JSON_pack_time_rel ("timeframe", - limit->timeframe), - TALER_JSON_pack_amount ("threshold", - &limit->threshold), - GNUNET_JSON_pack_bool ("soft_limit", - limit->soft_limit) - ); - GNUNET_assert (0 == - json_array_append_new (limits, - jl)); -} - - -/** * Return JSON array with AccountLimit objects giving * the current limits for this exchange. * @@ -753,12 +728,10 @@ return_auth_required ( kc->mi->settings.id, &ekr->h_wire, ekr->exchange_url, - ekr->exchange_kyc_serial, GNUNET_TIME_timestamp_get (), ks->hr.http_status, ks->hr.ec, NULL, - 0, NULL, false, true); @@ -838,12 +811,10 @@ exchange_check_cb ( kc->mi->settings.id, &ekr->h_wire, ekr->exchange_url, - ekr->exchange_kyc_serial, GNUNET_TIME_timestamp_get (), MHD_HTTP_NO_CONTENT, TALER_EC_NONE, NULL, - 0, NULL, false, true); @@ -885,12 +856,10 @@ exchange_check_cb ( kc->mi->settings.id, &ekr->h_wire, ekr->exchange_url, - ekr->exchange_kyc_serial, GNUNET_TIME_timestamp_get (), ks->hr.http_status, ks->hr.ec, NULL, - 0, NULL, false, true); @@ -951,6 +920,7 @@ determine_eligible_accounts ( /* For all accounts of the exchange */ for (unsigned int i = 0; i<keys->accounts_len; i++) { + /* FIXME: move into convenience function in libtalerexchange? See also taler-merchant-kyccheck.c! */ struct TALER_EXCHANGE_WireAccount *account = &keys->accounts[i]; bool account_restricted = false; @@ -1138,18 +1108,12 @@ kyc_status_cb ( <, STALE_KYC_TIMEOUT)) ) return; /* KYC ok, ignore! */ - if (0 == exchange_kyc_serial) - { - kc->kyc_serial_pending = true; - return; - } kc->response_code = MHD_HTTP_OK; ekr = GNUNET_new (struct ExchangeKycRequest); GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); ekr->h_wire = *h_wire; - ekr->exchange_kyc_serial = exchange_kyc_serial; ekr->exchange_url = GNUNET_strdup (exchange_url); ekr->payto_uri = GNUNET_strdup (payto_uri); ekr->last_check = last_check; diff --git a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c index 755ed4c9..56235514 100644 --- a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c +++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c @@ -67,6 +67,7 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, &details.duration), GNUNET_JSON_spec_end () }; + struct GNUNET_TIME_Relative validity; GNUNET_assert (NULL != mi); GNUNET_assert (NULL != slug); @@ -82,12 +83,12 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, : MHD_NO; } - struct GNUNET_TIME_Relative validity = GNUNET_TIME_absolute_get_difference ( + validity = GNUNET_TIME_absolute_get_difference ( details.valid_after.abs_time, details.valid_before.abs_time); - // Check if start_time is before valid_before - if (0 == validity.rel_value_us) + /* Check if start_time is before valid_before */ + if (GNUNET_TIME_relative_is_zero (validity)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); diff --git a/src/backend/taler-merchant-httpd_private-post-account.c b/src/backend/taler-merchant-httpd_private-post-account.c index dbaac7ba..44d9f084 100644 --- a/src/backend/taler-merchant-httpd_private-post-account.c +++ b/src/backend/taler-merchant-httpd_private-post-account.c @@ -123,6 +123,7 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh, struct TALER_MERCHANTDB_AccountDetails ad = { .payto_uri = wm->payto_uri, .salt = wm->wire_salt, + .instance_id = mi->settings.id, .h_wire = wm->h_wire, .credit_facade_url = wm->credit_facade_url, .credit_facade_credentials = wm->credit_facade_credentials, @@ -131,7 +132,6 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh, enum GNUNET_DB_QueryStatus qs; qs = TMH_db->insert_account (TMH_db->cls, - mi->settings.id, &ad); switch (qs) { diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 4761fbdc..8fcbf7c4 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -1544,23 +1544,26 @@ set_token_family (struct OrderContext *oc, return GNUNET_SYSERR; } - struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); - - /* Verify that the token family is valid right now. */ - if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after, >, now) - || - GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before, <=, now) - ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Token family expired or not yet valid\n"); - reply_with_error (oc, - /* TODO: HTTP Status Code GONE would be more elegant, - but that is already used to indicate that a product is out of stock. */ - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID, - key_details.token_family.slug); - return GNUNET_SYSERR; + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + + /* Verify that the token family is valid right now. */ + if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after, >, now) + || + GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before, <=, + now) + ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Token family expired or not yet valid\n"); + reply_with_error (oc, + /* TODO: HTTP Status Code GONE would be more elegant, + but that is already used to indicate that a product is out of stock. */ + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID, + key_details.token_family.slug); + return GNUNET_SYSERR; + } } /* slug is not needed */ @@ -1607,8 +1610,6 @@ set_token_family (struct OrderContext *oc, /* There is no matching key for this token family yet. */ /* We have to generate a fresh key pair. */ /* If public key is NULL, private key must also be NULL */ - GNUNET_assert (NULL == key_details.priv.private_key); - enum GNUNET_DB_QueryStatus iqs; struct GNUNET_CRYPTO_BlindSignPrivateKey *priv; struct GNUNET_CRYPTO_BlindSignPublicKey *pub; @@ -1617,6 +1618,7 @@ set_token_family (struct OrderContext *oc, GNUNET_TIME_absolute_add (min_valid_after.abs_time, key_details.token_family.duration)); + GNUNET_assert (NULL == key_details.priv.private_key); if (GNUNET_TIME_timestamp_cmp (min_valid_after, <, key_details.token_family.valid_after)) @@ -1640,55 +1642,55 @@ set_token_family (struct OrderContext *oc, /* TODO: Make cipher and key length configurable */ GNUNET_CRYPTO_BSA_RSA, 4096); - - struct TALER_TokenIssuePublicKeyP token_pub = { - .public_key = pub, - }; - struct TALER_TokenIssuePrivateKeyP token_priv = { - .private_key = priv, - }; - - iqs = TMH_db->insert_token_family_key (TMH_db->cls, - slug, - &token_pub, - &token_priv, - min_valid_after, - valid_before); - - GNUNET_CRYPTO_blind_sign_priv_decref (priv); - - if (iqs <= 0) { - enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - unsigned int http_status = 0; + struct TALER_TokenIssuePublicKeyP token_pub = { + .public_key = pub, + }; + struct TALER_TokenIssuePrivateKeyP token_priv = { + .private_key = priv, + }; - switch (iqs) + iqs = TMH_db->insert_token_family_key (TMH_db->cls, + slug, + &token_pub, + &token_priv, + min_valid_after, + valid_before); + GNUNET_CRYPTO_blind_sign_priv_decref (priv); + + if (iqs <= 0) { - case GNUNET_DB_STATUS_HARD_ERROR: - http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - ec = TALER_EC_GENERIC_DB_STORE_FAILED; - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* case listed to make compilers happy */ - GNUNET_assert (0); + enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + unsigned int http_status = 0; + + switch (iqs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_STORE_FAILED; + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* case listed to make compilers happy */ + GNUNET_assert (0); + } + + GNUNET_break (0); + reply_with_error (oc, + http_status, + ec, + "token_family_slug"); + return GNUNET_SYSERR; } - GNUNET_break (0); - reply_with_error (oc, - http_status, - ec, - "token_family_slug"); - return GNUNET_SYSERR; + key.pub = token_pub; + key.valid_after = min_valid_after; + key.valid_before = valid_before; } - - key.pub = token_pub; - key.valid_after = min_valid_after; - key.valid_before = valid_before; } else { @@ -1771,8 +1773,9 @@ serialize_order (struct OrderContext *oc) for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++) { json_t *keys = json_array (); - struct TALER_MerchantContractTokenFamily *family = &oc->parse_choices. - token_families[i]; + struct TALER_MerchantContractTokenFamily *family + = &oc->parse_choices.token_families[i]; + json_t *jfamily; for (unsigned int j = 0; j<family->keys_len; j++) { @@ -1797,11 +1800,13 @@ serialize_order (struct OrderContext *oc) key.valid_before) ); - GNUNET_assert (0 == json_array_append_new (keys, jkey)); + GNUNET_assert (0 == + json_array_append_new (keys, + jkey)); } /* TODO: Add 'details' field. */ - json_t *jfamily = GNUNET_JSON_PACK ( + jfamily = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("name", family->name), GNUNET_JSON_pack_string ("description", @@ -1822,19 +1827,22 @@ serialize_order (struct OrderContext *oc) for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) { - struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; - + const struct TALER_MerchantContractChoice *choice + = &oc->parse_choices.choices[i]; json_t *inputs = json_array (); json_t *outputs = json_array (); + GNUNET_assert (NULL != inputs); + GNUNET_assert (NULL != outputs); for (unsigned int j = 0; j<choice->inputs_len; j++) { - struct TALER_MerchantContractInput *input = &choice->inputs[j]; + const struct TALER_MerchantContractInput *input + = &choice->inputs[j]; + json_t *jinput; /* For now, only tokens are supported */ GNUNET_assert (TALER_MCIT_TOKEN == input->type); - - json_t *jinput = GNUNET_JSON_PACK ( + jinput = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("kind", TMH_string_from_contract_input_type (input-> type)), @@ -1846,17 +1854,20 @@ serialize_order (struct OrderContext *oc) input->details.token.valid_after) ); - GNUNET_assert (0 == json_array_append_new (inputs, jinput)); + GNUNET_assert (0 == + json_array_append_new (inputs, + jinput)); } for (unsigned int j = 0; j<choice->outputs_len; j++) { struct TALER_MerchantContractOutput *output = &choice->outputs[j]; + json_t *joutput; /* For now, only tokens are supported */ GNUNET_assert (TALER_MCOT_TOKEN == output->type); - json_t *joutput = GNUNET_JSON_PACK ( + joutput = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("kind", TMH_string_from_contract_output_type (output-> type)), @@ -1868,17 +1879,24 @@ serialize_order (struct OrderContext *oc) output->details.token.valid_after) ); - GNUNET_assert (0 == json_array_append (outputs, joutput)); + GNUNET_assert (0 == + json_array_append_new (outputs, + joutput)); } - json_t *jchoice = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_incref ("inputs", - inputs), - GNUNET_JSON_pack_array_incref ("outputs", - outputs) - ); + { + json_t *jchoice + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); - GNUNET_assert (0 == json_array_append (choices, jchoice)); + GNUNET_assert (0 == + json_array_append_new (choices, + jchoice)); + } } oc->serialize_order.contract = GNUNET_JSON_PACK ( diff --git a/src/backend/taler-merchant-httpd_statics.c b/src/backend/taler-merchant-httpd_statics.c index 72f81d85..a82dafba 100644 --- a/src/backend/taler-merchant-httpd_statics.c +++ b/src/backend/taler-merchant-httpd_statics.c @@ -316,6 +316,10 @@ TMH_statics_init () * Nicely shut down. */ void __attribute__ ((destructor)) +get_statics_fini (void); + +/* Declaration avoids compiler warning */ +void __attribute__ ((destructor)) get_statics_fini () { for (unsigned int i = 0; i<loaded_length; i++) diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c index 297106de..dfb2be53 100644 --- a/src/backend/taler-merchant-kyccheck.c +++ b/src/backend/taler-merchant-kyccheck.c @@ -22,7 +22,10 @@ #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <pthread.h> +#include <regex.h> #include <taler/taler_dbevents.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_exchange_service.h> #include "taler_merchant_bank_lib.h" #include "taler_merchantdb_lib.h" #include "taler_merchantdb_plugin.h" @@ -90,11 +93,32 @@ struct Account struct Inquiry *i_tail; /** + * Merchant instance this account belongs to. + */ + char *instance_id; + + /** * The payto-URI of this account. */ char *merchant_account_uri; /** + * Wire hash of the merchant bank account (with the + * respective salt). + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Private key of the instance. + */ + union TALER_AccountPrivateKeyP ap; + + /** + * Hash of the @e merchant_account_uri. + */ + struct TALER_PaytoHashP h_payto; + + /** * Database generation when this account * was last active. */ @@ -133,6 +157,54 @@ struct Inquiry struct Account *a; /** + * AccountLimits that apply to the account, NULL + * if unknown. + */ + json_t *jlimits; + + /** + * Handle for the actual HTTP request to the exchange. + */ + struct TALER_EXCHANGE_KycCheckHandle *kyc; + + /** + * Access token for the /kyc-info API. + */ + struct TALER_AccountAccessTokenP access_token; + + /** + * Last time we called the /kyc-check endpoint. + */ + struct GNUNET_TIME_Timestamp last_kyc_check; + + /** + * When is the next KYC check due? + */ + struct GNUNET_TIME_Absolute due; + + /** + * When should the current KYC time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Current exponential backoff. + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Last HTTP status returned by the exchange from + * the /kyc-check endpoint. + */ + unsigned int last_http_status; + + /** + * Last Taler error code returned by the exchange from + * the /kyc-check endpoint. + */ + enum TALER_ErrorCode last_ec; + + /** * Did we not run this inquiry due to limits? */ bool limited; @@ -143,10 +215,16 @@ struct Inquiry bool kyc_ok; /** - * True if we did this account's KYC AUTH transfer. + * True if merchant did perform this account's KYC AUTH transfer and @e access_token is set. */ bool auth_ok; + /** + * True if the account is known to be currently under + * investigation by AML staff. + */ + bool aml_review; + }; @@ -238,12 +316,13 @@ static bool at_limit; /** - * Do KYC check work for the give inquiry in @a i. + * Check about performing a /kyc-check request with the + * exchange for the given inquiry. * - * @param i inquiry to work on + * @param cls a `struct Inquiry` to process */ static void -inquiry_work (struct Inquiry *i); +inquiry_work (void *cls); /** @@ -292,10 +371,216 @@ end_inquiry (void) } +/** + * Pack the given @a limit into the JSON @a limits array. + * + * @param limit account limit to pack + * @param[in,out] limits JSON array to extend + */ +static void +pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit, + json_t *limits) +{ + json_t *jl; + + jl = GNUNET_JSON_PACK ( + TALER_JSON_pack_kycte ("operation_type", + limit->operation_type), + GNUNET_JSON_pack_time_rel ("timeframe", + limit->timeframe), + TALER_JSON_pack_amount ("threshold", + &limit->threshold), + GNUNET_JSON_pack_bool ("soft_limit", + limit->soft_limit) + ); + GNUNET_assert (0 == + json_array_append_new (limits, + jl)); +} + + +/** + * Store KYC response from the exchange in the + * local database. + * + * @param[in,out] i inquiry context, jlimits is updated + * @param ks HTTP response details + * @param account_kyc_status account KYC status details + */ static void -inquiry_work (struct Inquiry *i) +store_kyc_status ( + struct Inquiry *i, + const struct TALER_EXCHANGE_KycStatus *ks, + const struct TALER_EXCHANGE_AccountKycStatus *account_kyc_status) { - // enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qs; + json_t *jlimits; + + jlimits = json_array (); + GNUNET_assert (NULL != jlimits); + for (unsigned int j = 0; j<account_kyc_status->limits_length; j++) + { + const struct TALER_EXCHANGE_AccountLimit *limit + = &account_kyc_status->limits[j]; + + pack_limit (limit, + jlimits); + } + json_decref (i->jlimits); + i->jlimits = jlimits; + // FIXME: update more of i? + + qs = db_plugin->account_kyc_set_status ( + db_plugin->cls, + i->a->instance_id, + &i->a->h_wire, + i->e->keys->exchange_url, + GNUNET_TIME_timestamp_get (), + ks->hr.http_status, + ks->hr.ec, + &account_kyc_status->access_token, + jlimits, + account_kyc_status->aml_review, + MHD_HTTP_OK == ks->hr.http_status); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } +} + + +/** + * Function called with the result of a KYC check. + * + * @param cls a `struct Inquiry *` + * @param ks the account's KYC status details + */ +static void +exchange_check_cb ( + void *cls, + const struct TALER_EXCHANGE_KycStatus *ks) +{ + struct Inquiry *i = cls; + + i->kyc = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Checking KYC status of `%s' at `%s' is %u\n", + i->a->merchant_account_uri, + i->e->keys->exchange_url, + ks->hr.http_status); + switch (ks->hr.http_status) + { + case MHD_HTTP_OK: + store_kyc_status (i, + ks, + &ks->details.ok); + break; + case MHD_HTTP_ACCEPTED: + store_kyc_status (i, + ks, + &ks->details.accepted); + break; + case MHD_HTTP_NO_CONTENT: + { + enum GNUNET_DB_QueryStatus qs; + + // FIXME: update i? + qs = db_plugin->account_kyc_set_status ( + db_plugin->cls, + i->a->instance_id, + &i->a->h_wire, + i->e->keys->exchange_url, + GNUNET_TIME_timestamp_get (), + MHD_HTTP_NO_CONTENT, + TALER_EC_NONE, + NULL, + NULL, + false, + true); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + } + break; + case MHD_HTTP_FORBIDDEN: /* bad signature */ + case MHD_HTTP_NOT_FOUND: /* account unknown */ + case MHD_HTTP_CONFLICT: /* no account_pub known */ + { + enum GNUNET_DB_QueryStatus qs; + + // FIXME: update i? + qs = db_plugin->account_kyc_set_status ( + db_plugin->cls, + i->a->instance_id, + &i->a->h_wire, + i->e->keys->exchange_url, + GNUNET_TIME_timestamp_get (), + ks->hr.http_status, + ks->hr.ec, + NULL, + NULL, + false, + true); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + } + break; + default: + { + enum GNUNET_DB_QueryStatus qs; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Exchange responded with HTTP status %u (%d) to /kyc-check request!\n", + ks->hr.http_status, + ks->hr.ec); + // FIXME: update i? + qs = db_plugin->account_kyc_set_status ( + db_plugin->cls, + i->a->instance_id, + &i->a->h_wire, + i->e->keys->exchange_url, + GNUNET_TIME_timestamp_get (), + ks->hr.http_status, + ks->hr.ec, + NULL /* access token */, + NULL /* jlimits */, + false /* in_aml_review? well, unknown... */, + true /* kyc_ok? well, unknown... */); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + break; + } + } + end_inquiry (); +} + + +static void +inquiry_work (void *cls) +{ + struct Inquiry *i = cls; + + i->task = NULL; + // FIXME: update i->due, i->timeout, i->backoff + if (! GNUNET_TIME_absolute_is_past (i->due)) + goto finish; GNUNET_assert (OPEN_INQUIRY_LIMIT >= active_inquiries); if (OPEN_INQUIRY_LIMIT <= active_inquiries) @@ -308,21 +593,29 @@ inquiry_work (struct Inquiry *i) } at_limit = false; -#if 0 - active_inquiries++; - // FIXME: do actual work! - qs = db_plugin->select_open_transfers (db_plugin->cls, - limit, - &start_inquiry, - NULL); - if (qs < 0) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Checking KYC status of `%s' at `%s'\n", + i->a->merchant_account_uri, + i->e->keys->exchange_url); + i->kyc = TALER_EXCHANGE_kyc_check ( + ctx, + i->e->keys->exchange_url, + &i->a->h_payto, + &i->a->ap, + GNUNET_TIME_absolute_get_remaining (i->timeout), + &exchange_check_cb, + i); + if (NULL == i->kyc) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to obtain open transfers from database\n"); - GNUNET_SCHEDULER_shutdown (); - return; + GNUNET_break (0); + i->task + = GNUNET_SCHEDULER_add_at (i->timeout, + &inquiry_work, + i); + goto finish; } -#endif + active_inquiries++; +finish: if ( (0 == active_inquiries) && (test_mode) ) { @@ -335,6 +628,80 @@ inquiry_work (struct Inquiry *i) /** + * Check if the account @a could work with exchange that + * has keys @a keys. + * + * @param keys the keys of an exchange + * @param a an account + */ +static bool +is_eligible (struct TALER_EXCHANGE_Keys *keys, + struct Account *a) +{ + /* For all accounts of the exchange */ + for (unsigned int i = 0; i<keys->accounts_len; i++) + { + /* FIXME: move into convenience function in libtalerexchange? See also taler-merchant-httpd_private-get-instances-ID-kyc.c*/ + struct TALER_EXCHANGE_WireAccount *account + = &keys->accounts[i]; + bool account_restricted = false; + + /* KYC auth transfers are never supported with conversion */ + if (NULL != account->conversion_url) + continue; + + /* filter by source account by credit_restrictions */ + for (unsigned int j = 0; j<account->credit_restrictions_length; j++) + { + const struct TALER_EXCHANGE_AccountRestriction *ar + = &account->credit_restrictions[j]; + + switch (ar->type) + { + case TALER_EXCHANGE_AR_INVALID: + continue; + case TALER_EXCHANGE_AR_DENY: + account_restricted = true; + break; + case TALER_EXCHANGE_AR_REGEX: + { + regex_t ex; + bool allowed = false; + + if (0 != regcomp (&ex, + ar->details.regex.posix_egrep, + REG_NOSUB | REG_EXTENDED)) + { + GNUNET_break_op (0); + continue; + } + if (regexec (&ex, + a->merchant_account_uri, + 0, NULL, + REG_STARTEND)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Merchant account `%s' allowed by regex\n", + a->merchant_account_uri); + allowed = true; + } + regfree (&ex); + if (! allowed) + account_restricted = true; + break; + } + } /* end switch */ + } /* end loop over credit restrictions */ + if (account_restricted) + continue; + /* exchange account is allowed, add it */ + return true; + } + return false; +} + + +/** * Start the KYC checking for account @a at exchange @a e. * * @param e an exchange @@ -345,6 +712,7 @@ start_inquiry (struct Exchange *e, struct Account *a) { struct Inquiry *i; + enum GNUNET_DB_QueryStatus qs; i = GNUNET_new (struct Inquiry); i->e = e; @@ -352,7 +720,25 @@ start_inquiry (struct Exchange *e, GNUNET_CONTAINER_DLL_insert (a->i_head, a->i_tail, i); - // FIXME: initial import from DB here!? + qs = db_plugin->get_kyc_status (db_plugin->cls, + a->merchant_account_uri, + a->instance_id, + e->keys->exchange_url, + &i->auth_ok, + &i->access_token, + &i->kyc_ok, + &i->last_http_status, + &i->last_ec, + &i->last_kyc_check, + &i->aml_review, + &i->jlimits); + if (qs < 0) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } inquiry_work (i); } @@ -375,12 +761,46 @@ stop_inquiry (struct Inquiry *i) GNUNET_SCHEDULER_cancel (i->task); i->task = NULL; } - // FIXME: other clean-ups related to i here! + if (NULL != i->kyc) + { + TALER_EXCHANGE_kyc_check_cancel (i->kyc); + i->kyc = NULL; + } + if (NULL != i->jlimits) + { + json_decref (i->jlimits); + i->jlimits = NULL; + } GNUNET_free (i); } /** + * Stop KYC inquiry for account @a at exchange @a e. + * + * @param e an exchange + * @param a an account + */ +static void +stop_inquiry_at (struct Exchange *e, + struct Account *a) +{ + for (struct Inquiry *i = a->i_head; + NULL != i; + i = i->next) + { + if (e == i->e) + { + stop_inquiry (i); + return; + } + } + /* strange, there should have been a match! */ + GNUNET_break (0); +} + + +/** * Start inquries for all exchanges on account @a a. * * @param a an account @@ -391,8 +811,10 @@ start_inquiries (struct Account *a) for (struct Exchange *e = e_head; NULL != e; e = e->next) - start_inquiry (e, - a); + if (is_eligible (e->keys, + a)) + start_inquiry (e, + a); } @@ -415,17 +837,21 @@ stop_inquiries (struct Account *a) * Callback invoked with information about a bank account. * * @param cls closure + * @param merchant_priv private key of the merchant instance * @param ad details about the account */ static void account_cb ( void *cls, + const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_AccountDetails *ad) { const char *payto_uri = ad->payto_uri; if (! ad->active) return; + if (NULL == merchant_priv) + return; /* instance was deleted */ for (struct Account *a = a_head; NULL != a; a = a->next) @@ -442,7 +868,17 @@ account_cb ( struct Account *a = GNUNET_new (struct Account); a->account_gen = database_gen; - a->merchant_account_uri = GNUNET_strdup (payto_uri); + a->merchant_account_uri + = GNUNET_strdup (ad->payto_uri); + a->instance_id + = GNUNET_strdup (ad->instance_id); + a->h_wire + = ad->h_wire; + a->ap.merchant_priv + = *merchant_priv; + TALER_payto_hash (a->merchant_account_uri, + &a->h_payto); + GNUNET_CONTAINER_DLL_insert (a_head, a_tail, a); @@ -537,8 +973,28 @@ find_keys (const char *exchange_url) if (0 == strcmp (e->keys->exchange_url, keys->exchange_url)) { - TALER_EXCHANGE_keys_decref (e->keys); + struct TALER_EXCHANGE_Keys *old_keys = e->keys; + e->keys = keys; + for (struct Account *a = a_head; + NULL != a; + a = a->next) + { + bool was_eligible = is_eligible (old_keys, + a); + bool now_eligible = is_eligible (keys, + a); + + if (was_eligible == now_eligible) + continue; /* no change, do nothing */ + if (was_eligible) + stop_inquiry_at (e, + a); + else /* is_eligible */ + start_inquiry (e, + a); + } + TALER_EXCHANGE_keys_decref (old_keys); return; } } @@ -551,7 +1007,9 @@ find_keys (const char *exchange_url) NULL != a; a = a->next) { - if (a->account_gen < database_gen) + if ( (a->account_gen == database_gen) && + (is_eligible (e->keys, + a)) ) start_inquiry (e, a); } diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index 1703f20c..792bfb53 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -27,6 +27,7 @@ sql_DATA = \ merchant-0008.sql \ merchant-0009.sql \ merchant-0010.sql \ + merchant-0011.sql \ drop.sql BUILT_SOURCES = \ @@ -102,6 +103,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_lookup_pending_deposits.h pg_lookup_pending_deposits.c \ pg_insert_instance.h pg_insert_instance.c \ pg_account_kyc_set_status.h pg_account_kyc_set_status.c \ + pg_get_kyc_status.h pg_get_kyc_status.c \ pg_account_kyc_get_status.h pg_account_kyc_get_status.c \ pg_delete_instance_private_key.h pg_delete_instance_private_key.c \ pg_purge_instance.h pg_purge_instance.c \ diff --git a/src/backenddb/merchant-0010.sql b/src/backenddb/merchant-0010.sql index e2839aa4..44a2a113 100644 --- a/src/backenddb/merchant-0010.sql +++ b/src/backenddb/merchant-0010.sql @@ -15,7 +15,7 @@ -- -- @file merchant-0010.sql --- @brief Remove dead aml_decision column +-- @brief Remove dead aml_decision column and add new ones -- @author Christian Grothoff -- Everything in one big transaction diff --git a/src/backenddb/pg_account_kyc_get_status.c b/src/backenddb/pg_account_kyc_get_status.c index c2e97276..1eee0416 100644 --- a/src/backenddb/pg_account_kyc_get_status.c +++ b/src/backenddb/pg_account_kyc_get_status.c @@ -80,7 +80,7 @@ kyc_status_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { struct TALER_MerchantWireHashP h_wire; - uint64_t kyc_serial; + uint64_t kyc_serial = 0; /* deprecated */ char *exchange_url; char *payto_uri; struct GNUNET_TIME_Timestamp last_check; @@ -88,8 +88,6 @@ kyc_status_cb (void *cls, 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", @@ -164,7 +162,6 @@ TMH_PG_account_kyc_get_status ( "lookup_kyc_status", "SELECT" " h_wire" - ",exchange_kyc_serial" ",payto_uri" ",exchange_url" ",kyc_timestamp" diff --git a/src/backenddb/pg_account_kyc_set_status.c b/src/backenddb/pg_account_kyc_set_status.c index 2e2b7911..c4e8bd97 100644 --- a/src/backenddb/pg_account_kyc_set_status.c +++ b/src/backenddb/pg_account_kyc_set_status.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2024 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 @@ -17,6 +17,7 @@ * @file backenddb/pg_account_kyc_set_status.c * @brief Implementation of the account_kyc_set_status function for Postgres * @author Iván Ávalos + * @author Christian Grothoff */ #include "platform.h" #include <taler/taler_error_codes.h> @@ -32,43 +33,30 @@ TMH_PG_account_kyc_set_status ( const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, - uint64_t exchange_kyc_serial, struct GNUNET_TIME_Timestamp timestamp, unsigned int exchange_http_status, enum TALER_ErrorCode exchange_ec_code, const struct TALER_AccountAccessTokenP *access_token, - unsigned int num_limits, - const struct TALER_MERCHANTDB_DepositLimits *limits, + const json_t *jlimits, bool in_aml_review, bool kyc_ok) { struct PostgresClosure *pg = cls; uint32_t http_status32 = (uint32_t) exchange_http_status; uint32_t ec_code32 = (uint32_t) exchange_ec_code; - struct TALER_Amount thresholds[GNUNET_NZL (num_limits)]; - struct GNUNET_TIME_Relative timeframes[GNUNET_NZL (num_limits)]; - bool soft_limits[GNUNET_NZL (num_limits)]; 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_timestamp (×tamp), - GNUNET_PQ_query_param_uint64 (&exchange_kyc_serial), GNUNET_PQ_query_param_uint32 (&http_status32), GNUNET_PQ_query_param_uint32 (&ec_code32), NULL != access_token ? GNUNET_PQ_query_param_auto_from_type (access_token) : GNUNET_PQ_query_param_null (), - TALER_PQ_query_param_array_amount_with_currency ( - num_limits, - thresholds, - pg->conn), - GNUNET_PQ_query_param_array_rel_time (num_limits, - timeframes, - pg->conn), - GNUNET_PQ_query_param_array_bool (num_limits, - soft_limits, - pg->conn), + NULL != jlimits + ? TALER_PQ_query_param_json (jlimits) + : GNUNET_PQ_query_param_null (), GNUNET_PQ_query_param_bool (in_aml_review), GNUNET_PQ_query_param_bool (kyc_ok), GNUNET_PQ_query_param_end @@ -91,24 +79,19 @@ TMH_PG_account_kyc_set_status ( " out_no_instance AS no_instance" " ,out_no_account AS no_account" " FROM merchant_do_account_kyc_set_status" - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);"); - for (unsigned int i=0; i<num_limits; i++) - { - const struct TALER_MERCHANTDB_DepositLimits *limit - = &limits[i]; - - thresholds[i] = limit->threshold; - timeframes[i] = limit->timeframe; - soft_limits[i] = limit->soft_limit; - } + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, "account_kyc_set_status", params, rs); - GNUNET_PQ_cleanup_query_params_closures (params); if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); + GNUNET_break (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); return qs; + } GNUNET_break (! no_instance); GNUNET_break (! no_account); return qs; diff --git a/src/backenddb/pg_account_kyc_set_status.h b/src/backenddb/pg_account_kyc_set_status.h index 3c5891b3..d970c62e 100644 --- a/src/backenddb/pg_account_kyc_set_status.h +++ b/src/backenddb/pg_account_kyc_set_status.h @@ -32,13 +32,11 @@ * @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 timestamp timestamp to store * @param exchange_http_status HTTP status code returned last by the exchange * @param exchange_ec_code Taler error code returned last by the exchange * @param access_token access token for the KYC process, NULL for none - * @param num_limits length of the @a limits array - * @param limits array with deposit limits returned by the exchange + * @param jlimits JSON array with AccountLimits returned by the exchange * @param in_aml_review true if the exchange says the account is under review * @param kyc_ok current KYC status (true for satisfied) * @return database result code @@ -49,13 +47,11 @@ TMH_PG_account_kyc_set_status ( const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, - uint64_t exchange_kyc_serial, struct GNUNET_TIME_Timestamp timestamp, unsigned int exchange_http_status, enum TALER_ErrorCode exchange_ec_code, const struct TALER_AccountAccessTokenP *access_token, - unsigned int num_limits, - const struct TALER_MERCHANTDB_DepositLimits *limits, + const json_t *jlimits, bool in_aml_review, bool kyc_ok); diff --git a/src/backenddb/pg_account_kyc_set_status.sql b/src/backenddb/pg_account_kyc_set_status.sql index f72ab6ec..ce86a764 100644 --- a/src/backenddb/pg_account_kyc_set_status.sql +++ b/src/backenddb/pg_account_kyc_set_status.sql @@ -15,18 +15,17 @@ -- -CREATE OR REPLACE FUNCTION merchant_do_account_kyc_set_status ( +DROP FUNCTION IF EXISTS merchant_do_account_kyc_set_status; + +CREATE FUNCTION merchant_do_account_kyc_set_status ( IN in_merchant_id TEXT, IN in_h_wire BYTEA, IN in_exchange_url TEXT, IN in_timestamp INT8, - IN in_exchange_kyc_serial INT8, IN in_exchange_http_status INT4, IN in_exchange_ec_code INT4, - IN in_access_token BYTEA, - IN ina_thresholds taler_amount_currency[], - IN ina_timeframes INT8[], - IN ina_soft_limits BOOL[], + IN in_access_token BYTEA, -- can be NULL + IN in_jlimits TEXT, IN in_aml_active BOOL, IN in_kyc_ok BOOL, OUT out_no_instance BOOL, @@ -36,8 +35,6 @@ AS $$ DECLARE my_merchant_id INT8; my_account_serial INT8; - ini_cat INT8; - rec RECORD; BEGIN out_no_instance=FALSE; @@ -67,49 +64,40 @@ THEN RETURN; END IF; -INSERT INTO merchant_kyc - (kyc_timestamp - ,kyc_ok - ,exchange_kyc_serial - ,account_serial - ,exchange_url - ,deposit_thresholds - ,deposit_timeframes - ,deposit_limits_are_soft - ,aml_review - ,exchange_http_status - ,exchange_ec_code - ,access_token) -VALUES - (in_timestamp - ,in_kyc_ok - ,in_exchange_kyc_serial - ,my_account_serial - ,in_exchange_url - ,ina_thresholds - ,ina_timeframes - ,ina_soft_limits - ,in_aml_active - ,in_exchange_http_status - ,in_exchange_ec_code - ,in_access_token) - ON CONFLICT DO NOTHING; +UPDATE merchant_kyc + SET kyc_timestamp=in_timestamp + ,kyc_ok=in_kyc_ok + ,jaccount_limits=in_jlimits + ,aml_review=in_aml_active + ,exchange_http_status=in_exchange_http_status + ,exchange_ec_code=in_exchange_ec_code + ,access_token=in_access_token + WHERE account_serial=my_account_serial + AND exchange_url=in_exchange_url; IF NOT FOUND THEN - UPDATE merchant_kyc - SET exchange_kyc_serial=in_exchange_kyc_serial - ,kyc_timestamp=in_timestamp - ,kyc_ok=in_kyc_ok - ,deposit_thresholds=ina_thresholds - ,deposit_timeframes=ina_timeframes - ,deposit_limits_are_soft=ina_soft_limits - ,aml_review=in_aml_active - ,exchange_http_status=in_exchange_http_status - ,exchange_ec_code=in_exchange_ec_code - ,access_token=in_access_token - WHERE account_serial=my_account_serial - AND exchange_url=in_exchange_url; + + INSERT INTO merchant_kyc + (kyc_timestamp + ,kyc_ok + ,account_serial + ,exchange_url + ,jaccount_limits + ,aml_review + ,exchange_http_status + ,exchange_ec_code + ,access_token) + VALUES + (in_timestamp + ,in_kyc_ok + ,my_account_serial + ,in_exchange_url + ,in_jlimits + ,in_aml_active + ,in_exchange_http_status + ,in_exchange_ec_code + ,in_access_token); END IF; -- Success! diff --git a/src/backenddb/pg_get_kyc_status.c b/src/backenddb/pg_get_kyc_status.c new file mode 100644 index 00000000..5c980bfd --- /dev/null +++ b/src/backenddb/pg_get_kyc_status.c @@ -0,0 +1,109 @@ +/* + This file is part of TALER + Copyright (C) 2024 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/> + */ +/** + * @file backenddb/pg_get_kyc_status.c + * @brief Implementation of the get_kyc_status function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_get_kyc_status.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TMH_PG_get_kyc_status ( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *auth_ok, + struct TALER_AccountAccessTokenP *access_token, + bool *kyc_ok, + unsigned int *last_http_status, + enum TALER_ErrorCode *last_ec, + struct GNUNET_TIME_Timestamp *last_kyc_check, + bool *aml_review, + json_t **jlimits) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (merchant_account_uri), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_end + }; + uint32_t h32 = 0; + uint32_t e32 = 0; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("access_token", + &access_token), + auth_ok), + GNUNET_PQ_result_spec_uint32 ("exchange_http_status", + &h32), + GNUNET_PQ_result_spec_uint32 ("exchange_ec_code", + &e32), + GNUNET_PQ_result_spec_bool ("kyc_ok", + kyc_ok), + GNUNET_PQ_result_spec_timestamp ("kyc_timestamp", + last_kyc_check), + GNUNET_PQ_result_spec_bool ("aml_review", + aml_review), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("jaccount_limits", + jlimits), + NULL), + GNUNET_PQ_result_spec_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + PREPARE (pg, + "get_kyc_status", + "SELECT" + " access_token" + ",exchange_http_status" + ",exchange_ec_code" + ",kyc_ok" + ",kyc_timestamp" + ",aml_review" + ",jaccount_limits" + " FROM merchant_kyc mk" + " JOIN merchant_accounts" + " USING (merchant_serial)" + " JOIN merchant_kyc" + " USING (account_serial)" + " WHERE exchange_url=$3" + " AND account_serial=" + " (SELECT account_serial" + " FROM merchant_accounts" + " WHERE payto_uri=$1" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$2));"); + *jlimits = NULL; + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_kyc_status", + params, + rs); + *last_ec = (enum TALER_ErrorCode) (int) e32; + *last_http_status = (unsigned int) h32; + return qs; +} diff --git a/src/backenddb/pg_get_kyc_status.h b/src/backenddb/pg_get_kyc_status.h new file mode 100644 index 00000000..729f37d5 --- /dev/null +++ b/src/backenddb/pg_get_kyc_status.h @@ -0,0 +1,62 @@ +/* + This file is part of TALER + Copyright (C) 2024 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/> + */ +/** + * @file backenddb/pg_get_kyc_status.h + * @brief implementation of the get_kyc_status function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_GET_KYC_STATUS_H +#define PG_GET_KYC_STATUS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Check an account's KYC status at an exchange. + * + * @param cls closure + * @param merchant_payto_uri merchant backend instance ID + * @param instance_id the instance for which to check + * @param exchange_url base URL of the exchange + * @param[out] auth_ok true if @a access_token was set + * @param[out] access_token set to access token for /kyc-info + * @param[out] kyc_ok true if no urgent KYC work must be done for this account + * @param[out] last_http_status set to last HTTP status from exchange on /kyc-check + * @param[out] last_ec set to last Taler error code from exchange on /kyc-check + * @param[out] last_kyc_check set to time of last KYC check + * @param[out] aml_review set to true if the account is under AML review (if this exposed) + * @param[out] jlimits set to JSON array with AccountLimits, NULL if unknown (and likely defaults apply or KYC auth is urgently needed, see @a auth_ok) + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_get_kyc_status ( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *auth_ok, + struct TALER_AccountAccessTokenP *access_token, + bool *kyc_ok, + unsigned int *last_http_status, + enum TALER_ErrorCode *last_ec, + struct GNUNET_TIME_Timestamp *last_kyc_check, + bool *aml_review, + json_t **jlimits); + + +#endif diff --git a/src/backenddb/pg_insert_account.c b/src/backenddb/pg_insert_account.c index 04b0637c..504c4da5 100644 --- a/src/backenddb/pg_insert_account.c +++ b/src/backenddb/pg_insert_account.c @@ -29,12 +29,11 @@ enum GNUNET_DB_QueryStatus TMH_PG_insert_account ( void *cls, - const char *id, const struct TALER_MERCHANTDB_AccountDetails *account_details) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (id), + GNUNET_PQ_query_param_string (account_details->instance_id), GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire), GNUNET_PQ_query_param_auto_from_type (&account_details->salt), GNUNET_PQ_query_param_string (account_details->payto_uri), diff --git a/src/backenddb/pg_insert_account.h b/src/backenddb/pg_insert_account.h index 463bc527..758ce59e 100644 --- a/src/backenddb/pg_insert_account.h +++ b/src/backenddb/pg_insert_account.h @@ -29,14 +29,12 @@ * Insert information about an instance's account into our database. * * @param cls closure - * @param id identifier of the instance * @param account_details details about the account * @return database result code */ enum GNUNET_DB_QueryStatus TMH_PG_insert_account ( void *cls, - const char *id, const struct TALER_MERCHANTDB_AccountDetails *account_details); diff --git a/src/backenddb/pg_select_account.c b/src/backenddb/pg_select_account.c index 9d48e421..eab30377 100644 --- a/src/backenddb/pg_select_account.c +++ b/src/backenddb/pg_select_account.c @@ -57,6 +57,7 @@ TMH_PG_select_account (void *cls, }; ad->h_wire = *h_wire; + ad->instance_id = id; check_connection (pg); PREPARE (pg, "select_account", diff --git a/src/backenddb/pg_select_account_by_uri.c b/src/backenddb/pg_select_account_by_uri.c index fafae088..70471eb4 100644 --- a/src/backenddb/pg_select_account_by_uri.c +++ b/src/backenddb/pg_select_account_by_uri.c @@ -59,6 +59,7 @@ TMH_PG_select_account_by_uri (void *cls, ad->credit_facade_url = NULL; ad->credit_facade_credentials = NULL; ad->payto_uri = GNUNET_strdup (payto_uri); + ad->instance_id = id; check_connection (pg); PREPARE (pg, "select_account_by_uri", diff --git a/src/backenddb/pg_select_accounts.c b/src/backenddb/pg_select_accounts.c index eaf26015..672db926 100644 --- a/src/backenddb/pg_select_accounts.c +++ b/src/backenddb/pg_select_accounts.c @@ -72,9 +72,12 @@ select_account_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { char *payto; + char *instance_id; char *facade_url = NULL; json_t *credential = NULL; struct TALER_MERCHANTDB_AccountDetails acc; + struct TALER_MerchantPrivateKeyP merchant_priv; + bool no_priv; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("h_wire", &acc.h_wire), @@ -82,6 +85,8 @@ select_account_cb (void *cls, &acc.salt), GNUNET_PQ_result_spec_string ("payto_uri", &payto), + GNUNET_PQ_result_spec_string ("merchant_id", + &instance_id), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_string ("credit_facade_url", &facade_url), @@ -92,6 +97,10 @@ select_account_cb (void *cls, NULL), GNUNET_PQ_result_spec_bool ("active", &acc.active), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("merchant_priv", + &merchant_priv), + &no_priv), GNUNET_PQ_result_spec_end }; @@ -104,10 +113,12 @@ select_account_cb (void *cls, lic->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } + acc.instance_id = instance_id; acc.payto_uri = payto; acc.credit_facade_url = facade_url; acc.credit_facade_credentials = credential; lic->cb (lic->cb_cls, + no_priv ? NULL : &merchant_priv, &acc); GNUNET_PQ_cleanup_result (rs); } @@ -138,16 +149,22 @@ TMH_PG_select_accounts (void *cls, PREPARE (pg, "select_accounts", "SELECT" - " h_wire" - ",salt" - ",payto_uri" - ",credit_facade_url" - ",credit_facade_credentials" - ",active" - " FROM merchant_accounts" + " ma.h_wire" + ",ma.salt" + ",ma.payto_uri" + ",ma.credit_facade_url" + ",ma.credit_facade_credentials" + ",ma.active" + ",mk.merchant_priv" + ",mi.merchant_id" + " FROM merchant_accounts ma" + " JOIN merchant_instances mi" + " ON (mi.merchant_serial=ma.merchant_serial)" + " LEFT JOIN merchant_keys mk" + " ON (mk.merchant_serial=ma.merchant_serial)" " WHERE" - " ($1 IS NULL) OR" - " (merchant_serial=" + " ($1::TEXT IS NULL) OR" + " (ma.merchant_serial=" " (SELECT merchant_serial " " FROM merchant_instances" " WHERE merchant_id=$1));"); diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 2bb74d32..690f066e 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -32,6 +32,7 @@ #include "taler_merchantdb_plugin.h" #include "pg_helper.h" #include "pg_insert_otp.h" +#include "pg_get_kyc_status.h" #include "pg_delete_otp.h" #include "pg_update_otp.h" #include "pg_select_otp.h" @@ -581,6 +582,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_lookup_categories; plugin->select_category_by_name = &TMH_PG_select_category_by_name; + plugin->get_kyc_status + = &TMH_PG_get_kyc_status; plugin->select_category = &TMH_PG_select_category; plugin->update_category diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index c2028261..392511e1 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -98,7 +98,7 @@ struct InstanceData * @param instance the instance data to be filled. */ static void -make_instance (char *instance_id, +make_instance (const char *instance_id, struct InstanceData *instance) { memset (instance, @@ -451,7 +451,6 @@ test_insert_account (const struct InstanceData *instance, { TEST_COND_RET_ON_FAIL (expected_result == plugin->insert_account (plugin->cls, - instance->instance.id, account), "Insert account failed\n"); return 0; @@ -513,7 +512,11 @@ pre_test_instances (struct TestInstances_Closure *cls) /* Accounts */ make_account (&cls->accounts[0]); + cls->accounts[0].instance_id + = cls->instances[0].instance.id; make_account (&cls->accounts[1]); + cls->accounts[1].instance_id + = cls->instances[1].instance.id; } @@ -3207,7 +3210,7 @@ pre_test_deposits (struct TestDeposits_Closure *cls) /* Account */ make_account (&cls->account); - + cls->account.instance_id = cls->instance.instance.id; /* Signing key */ make_exchange_signkey (&cls->signkey); @@ -4325,7 +4328,7 @@ pre_test_transfers (struct TestTransfers_Closure *cls) /* Account */ make_account (&cls->account); - + cls->account.instance_id = cls->instance.instance.id; /* Order */ make_order ("test_transfers_od_1", &cls->order); @@ -5039,7 +5042,7 @@ pre_test_refunds (struct TestRefunds_Closure *cls) /* Account */ make_account (&cls->account); - + cls->account.instance_id = cls->instance.instance.id; /* Signing key */ make_exchange_signkey (&cls->signkey); @@ -5357,6 +5360,7 @@ pre_test_lookup_orders_all_filters ( make_instance ("test_inst_lookup_orders_all_filters", &cls->instance); make_account (&cls->account); + cls->account.instance_id = cls->instance.instance.id; make_exchange_signkey (&cls->signkey); for (unsigned int i = 0; i < 64; ++i) { @@ -5613,6 +5617,7 @@ test_kyc (void) make_instance ("test_kyc", &instance); make_account (&account); + account.instance_id = instance.instance.id; TEST_RET_ON_FAIL (test_insert_instance (&instance, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); TEST_RET_ON_FAIL (test_insert_account (&instance, @@ -5624,12 +5629,10 @@ test_kyc (void) instance.instance.id, &account.h_wire, "https://exchange.net/", - 1LLU, now, MHD_HTTP_OK, TALER_EC_NONE, NULL, - 0, NULL, false, false)); @@ -5638,12 +5641,10 @@ test_kyc (void) instance.instance.id, &account.h_wire, "https://exchange2.com/", - 1LLU, now, MHD_HTTP_OK, TALER_EC_NONE, NULL, - 0, NULL, false, false)); @@ -5652,12 +5653,10 @@ test_kyc (void) instance.instance.id, &account.h_wire, "https://exchange.net/", - 1LLU, now, MHD_HTTP_OK, TALER_EC_NONE, NULL, - 0, NULL, false, true)); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 7882f789..00fe71fe 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -118,6 +118,13 @@ struct TALER_MERCHANTDB_AccountDetails struct TALER_WireSaltP salt; /** + * Instance ID. Do not free (may be aliased with + * the instance ID given in the query!). + * FIXME: set in all functions involving this struct! + */ + const char *instance_id; + + /** * Actual account address as a payto://-URI. */ char *payto_uri; @@ -257,11 +264,13 @@ typedef void * Callback invoked with information about a bank account. * * @param cls closure + * @param merchant_priv private key of the merchant instance * @param ad details about the account */ typedef void (*TALER_MERCHANTDB_AccountCallback)( void *cls, + const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_AccountDetails *ad); @@ -1463,14 +1472,12 @@ struct TALER_MERCHANTDB_Plugin * Insert information about an instance's account into our database. * * @param cls closure - * @param id identifier of the instance * @param account_details details about the account * @return database result code */ enum GNUNET_DB_QueryStatus (*insert_account)( void *cls, - const char *id, const struct TALER_MERCHANTDB_AccountDetails *account_details); @@ -1700,19 +1707,50 @@ struct TALER_MERCHANTDB_Plugin void *kyc_cb_cls); /** + * Check an account's KYC status at an exchange. + * + * @param cls closure + * @param merchant_payto_uri merchant backend instance ID + * @param instance_id the instance for which to check + * @param exchange_url base URL of the exchange + * @param[out] auth_ok true if @a access_token was set + * @param[out] access_token set to access token for /kyc-info + * @param[out] kyc_ok true if no urgent KYC work must be done for this account + * @param[out] last_http_status set to last HTTP status from exchange on /kyc-check + * @param[out] last_ec set to last Taler error code from exchange on /kyc-check + * @param[out] last_kyc_check set to time of last KYC check + * @param[out] aml_review set to true if the account is under AML review (if this exposed) + * @param[out] jlimits set to JSON array with AccountLimits, NULL if unknown (and likely defaults apply or KYC auth is urgently needed, see @a auth_ok) + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*get_kyc_status)( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *auth_ok, + struct TALER_AccountAccessTokenP *access_token, + bool *kyc_ok, + unsigned int *last_http_status, + enum TALER_ErrorCode *last_ec, + struct GNUNET_TIME_Timestamp *last_kyc_check, + bool *aml_review, + json_t **jlimits); + + + /** * 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 timestamp timestamp to store * @param exchange_http_status HTTP status code returned last by the exchange * @param exchange_ec_code Taler error code returned last by the exchange * @param access_token access token for the KYC process, NULL for none - * @param num_limits length of the @a limits array - * @param limits array with deposit limits returned by the exchange + * @param jlimits JSON array with AccountLimits returned by the exchange * @param in_aml_review true if the exchange says the account is under review * @param kyc_ok current KYC status (true for satisfied) * @return database result code @@ -1723,13 +1761,11 @@ struct TALER_MERCHANTDB_Plugin const char *merchant_id, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, - uint64_t exchange_kyc_serial, struct GNUNET_TIME_Timestamp timestamp, unsigned int exchange_http_status, enum TALER_ErrorCode exchange_ec_code, const struct TALER_AccountAccessTokenP *access_token, - unsigned int num_limits, - const struct TALER_MERCHANTDB_DepositLimits *limits, + const json_t *jlimits, bool in_aml_review, bool kyc_ok); |