diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-10-10 14:39:24 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-10-10 14:39:24 +0200 |
commit | e668bcdeb5317f99f39957425ce735139c01810d (patch) | |
tree | ab6970df9a6679609019d253a75a27437f24b57d | |
parent | e3fcff1c567abf103b788bfe7fcc666ec82533d9 (diff) | |
download | merchant-e668bcdeb5317f99f39957425ce735139c01810d.tar.gz merchant-e668bcdeb5317f99f39957425ce735139c01810d.tar.bz2 merchant-e668bcdeb5317f99f39957425ce735139c01810d.zip |
more skeleton logic for merchant's new /kyc handler
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 1 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.h | 2 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 36 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c | 573 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h | 8 |
5 files changed, 591 insertions, 29 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index feb90b28..1023c08c 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -269,6 +269,7 @@ do_shutdown (void *cls) (void) cls; TMH_force_ac_resume (); TMH_force_pc_resume (); + TMH_force_kyc_resume (); TMH_force_rc_resume (); TMH_force_post_transfers_resume (); TMH_force_tip_pickup_resume (); diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index 60bd9ce3..bdb99704 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -663,7 +663,7 @@ TMH_reload_instances (const char *id); * @param hash the hash to check against * @return #GNUNET_OK if the @a token matches */ -int +enum GNUNET_GenericReturnValue TMH_check_auth (const char *token, const struct GNUNET_ShortHashCode *salt, const struct GNUNET_HashCode *hash); diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index 7ec76fc5..72abde23 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -416,10 +416,6 @@ abort_active_deposits (struct PayContext *pc) } -/** - * Force all pay contexts to be resumed as we are about - * to shut down MHD. - */ void TMH_force_pc_resume () { @@ -1611,10 +1607,11 @@ parse_pay (struct MHD_Connection *connection, { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return (MHD_YES == TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "'coins' must be an array")) + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "'coins' must be an array")) ? GNUNET_NO : GNUNET_SYSERR; } @@ -1924,24 +1921,15 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, GNUNET_assert (GNUNET_NO == pc->suspended); if (0 != pc->response_code) { - MHD_RESULT res; - /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == pc->response_code) { GNUNET_break (0); return MHD_NO; /* hard error */ } - res = MHD_queue_response (connection, - pc->response_code, - pc->response); - MHD_destroy_response (pc->response); - pc->response = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response (%u) for POST /orders/$ID/pay (%s).\n", - (unsigned int) pc->response_code, - res ? "OK" : "FAILED"); - return res; + return MHD_queue_response (connection, + pc->response_code, + pc->response); } { enum GNUNET_GenericReturnValue ret; @@ -1961,10 +1949,10 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending pay handling while working with the exchange\n"); GNUNET_assert (NULL == pc->timeout_task); - pc->timeout_task = - GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), - &handle_pay_timeout, - pc); + pc->timeout_task + = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), + &handle_pay_timeout, + pc); execute_pay_transaction (pc); return MHD_YES; } 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 b3422f3f..bc9aa76f 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 @@ -29,6 +29,426 @@ /** + * We do not re-check an acceptable KYC status for + * a month, as usually a KYC never expires. + */ +#define STALE_KYC_TIMEOUT GNUNET_TIME_UNIT_MONTHS + + +/** + * Information we keep per /kyc request. + */ +struct KycContext; + + +/** + * Structure for tracking requests to the exchange's + * ``/kyc-check`` API. + */ +struct ExchangeKycRequest +{ + /** + * Kept in a DLL. + */ + struct ExchangeKycRequest *next; + + /** + * Kept in a DLL. + */ + struct ExchangeKycRequest *prev; + + /** + * KYC request this exchange request is made for. + */ + struct KycContext *kc; + + /** + * Hash of the wire account (with salt) we are checking. + */ + struct GNUNET_HashCode h_wire; + + /** + * Handle for the actual HTTP request to the exchange. + */ + // struct TALER_EXCHANGE_KycHandle *kyc; + + /** + * KYC number used by the exchange. + */ + uint64_t exchange_kyc_serial; + + /** + * Our account's payto URI. + */ + char *payto_uri; + + /** + * Base URL of the exchange. + */ + char *exchange_url; + + /** + * Timestamp when we last got a reply from the exchange. + */ + struct GNUNET_TIME_Absolute last_check; + + /** + * Last KYC status returned by the exchange. + */ + bool kyc_ok; +}; + + +/** + * Information we keep per /kyc request. + */ +struct KycContext +{ + /** + * Stored in a DLL. + */ + struct KycContext *next; + + /** + * Stored in a DLL. + */ + struct KycContext *prev; + + /** + * Connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Instance we are serving. + */ + struct TMH_MerchantInstance *mi; + + /** + * Our handler context. + */ + struct TMH_HandlerContext *hc; + + /** + * Task to trigger on request timeout, or NULL. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * Response to return, NULL if we don't have one yet. + */ + struct MHD_Response *response; + + /** + * JSON array where we are building up the array with + * pending KYC operations. + */ + json_t *pending_kycs; + + /** + * JSON array where we are building up the array with + * troubled KYC operations. + */ + json_t *timeout_kycs; + + /** + * Head of DLL of requests we are making to an + * exchange to inquire about the latest KYC status. + */ + struct ExchangeKycRequest *exchange_pending_head; + + /** + * Tail of DLL of requests we are making to an + * exchange to inquire about the latest KYC status. + */ + struct ExchangeKycRequest *exchange_pending_tail; + + /** + * Set to the exchange URL, or NULL to not filter by + * exchange. + */ + const char *exchange_url; + + /** + * Set to the h_wire of the merchant account if + * @a have_h_wire is true, used to filter by account. + */ + struct GNUNET_HashCode h_wire; + + /** + * How long are we willing to wait for the exchange(s)? + */ + struct GNUNET_TIME_Relative timeout; + + /** + * HTTP status code to use for the reply, i.e 200 for "OK". + * Special value UINT_MAX is used to indicate hard errors + * (no reply, return #MHD_NO). + */ + unsigned int response_code; + + /** + * #GNUNET_NO if the @e connection was not suspended, + * #GNUNET_YES if the @e connection was suspended, + * #GNUNET_SYSERR if @e connection was resumed to as + * part of #MH_force_pc_resume during shutdown. + */ + enum GNUNET_GenericReturnValue suspended; + + /** + * True if @e h_wire was given. + */ + bool have_h_wire; +}; + + +/** + * Head of DLL. + */ +static struct KycContext *kc_head; + +/** + * Tail of DLL. + */ +static struct KycContext *kc_tail; + + +void +TMH_force_kyc_resume () +{ + for (struct KycContext *kc = kc_head; + NULL != kc; + kc = kc->next) + { + if (NULL != kc->timeout_task) + { + GNUNET_SCHEDULER_cancel (kc->timeout_task); + kc->timeout_task = NULL; + } + if (GNUNET_YES == kc->suspended) + { + kc->suspended = GNUNET_SYSERR; + MHD_resume_connection (kc->connection); + } + } +} + + +/** + * Custom cleanup routine for a `struct KycContext`. + * + * @param cls the `struct KycContext` to clean up. + */ +static void +kyc_context_cleanup (void *cls) +{ + struct KycContext *kc = cls; + struct ExchangeKycRequest *ekr; + + while (NULL != (ekr = kc->exchange_pending_head)) + { + GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, + kc->exchange_pending_tail, + ekr); + // FIXME: TALER_EXCHANGE_kyc_check_cancel (ekr->kyc); + GNUNET_free (ekr->exchange_url); + GNUNET_free (ekr->payto_uri); + GNUNET_free (ekr); + } + if (NULL != kc->timeout_task) + { + GNUNET_SCHEDULER_cancel (kc->timeout_task); + kc->timeout_task = NULL; + } + if (NULL != kc->response) + { + MHD_destroy_response (kc->response); + kc->response = NULL; + } + GNUNET_CONTAINER_DLL_remove (kc_head, + kc_tail, + kc); + json_decref (kc->pending_kycs); + json_decref (kc->timeout_kycs); + GNUNET_free (kc); +} + + +/** + * Resume the given KYC context and send the given response. + * Stores the response in the @a kc and signals MHD to resume + * the connection. Also ensures MHD runs immediately. + * + * @param kc KYC context + * @param response_code response code to use + * @param response response data to send back + */ +static void +resume_kyc_with_response (struct KycContext *kc, + unsigned int response_code, + struct MHD_Response *response) +{ + kc->response_code = response_code; + kc->response = response; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming /kyc handling as exchange interaction is done (%u)\n", + response_code); + if (NULL != kc->timeout_task) + { + GNUNET_SCHEDULER_cancel (kc->timeout_task); + kc->timeout_task = NULL; + } + GNUNET_assert (GNUNET_YES == kc->suspended); + kc->suspended = GNUNET_NO; + MHD_resume_connection (kc->connection); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ +} + + +/** + * Resume KYC processing with an error. + * + * @param kc operation to resume + * @param http_status http status code to return + * @param ec taler error code to return + * @param msg human readable error message + */ +static void +resume_kyc_with_error (struct KycContext *kc, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *msg) +{ + resume_kyc_with_response (kc, + http_status, + TALER_MHD_make_error (ec, + msg)); +} + + +/** + * Handle a timeout for the processing of the kyc request. + * + * @param cls our `struct KycContext` + */ +static void +handle_kyc_timeout (void *cls) +{ + struct KycContext *kc = cls; + + kc->timeout_task = NULL; + GNUNET_assert (GNUNET_YES == kc->suspended); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resuming KYC with error after timeout\n"); + // FIXME: we _still_ need to produce a valid + // JSON reply, not just an error according to the spec! + resume_kyc_with_error (kc, + MHD_HTTP_GATEWAY_TIMEOUT, + TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, + NULL); +} + + +#if FIXME +static void +exchange_check_cb (void *cls, + ...) +{ + struct ExchangeKycRequest *ekr = cls; + struct KycContext *kc = ekr->kc; + + ekr->kyc = NULL; + // build up reply in 'kc' + if (error_reply) + { + // logging ... + kc->response_code = MHD_HTTP_BAD_GATEWAY; + } + else + { + // ... + } + + GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, + kc->exchange_pending_tail, + ekr); + GNUNET_free (ekr->exchange_url); + GNUNET_free (ekr->payto_uri); + GNUNET_free (ekr); + if (NULL != kc->exchange_pending_head) + return; /* wait for more */ + /* All exchange requests done, create final + big respose from cummulated replies */ + if ( (0 == json_array_size (kc->pending_kycs)) && + (0 == json_array_size (kc->timeout_kycs)) ) + { + /* special case: all KYC operations did succeed + after we asked at the exchanges => 204 */ + response = empty; + resume_kyc_with_response (kc, + MHD_HTTP_NO_CONTENT, + response); + return; + } + response = make_response (kc); + resume_kyc_with_response (kc, + kc->response_code, + response); +} + + +#endif + + +/** + * Function called from ``account_kyc_get_status`` + * with KYC status information for this merchant. + * + * @param cls our `struct KycContext *` + * @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 + */ +static void +kyc_status_cb (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) +{ + struct KycContext *kc = cls; + struct ExchangeKycRequest *ekr; + + if (kyc_ok && + (GNUNET_TIME_absolute_get_duration (last_check).rel_value_us < + STALE_KYC_TIMEOUT.rel_value_us) ) + return; /* KYC ok, ignore! */ + kc->response_code = MHD_HTTP_ACCEPTED; + 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; + ekr->kyc_ok = kyc_ok; + ekr->kc = kc; +#if FIXME + ekr->kyc = TALER_EXCHANGE_kyc_check (exchange_url, + &exchange_check_cb, + ekr); +#endif +} + + +/** * Check the KYC status of an instance. * * @param mi instance to check KYC status of @@ -41,6 +461,151 @@ get_instances_ID_kyc (struct TMH_MerchantInstance *mi, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { + struct KycContext *kc = hc->ctx; + + if (NULL == kc) + { + kc = GNUNET_new (struct KycContext); + hc->ctx = kc; + hc->cc = &kyc_context_cleanup; + GNUNET_CONTAINER_DLL_insert (kc_head, + kc_tail, + kc); + kc->connection = connection; + kc->hc = hc; + kc->pending_kycs = json_array (); + GNUNET_assert (NULL != kc->pending_kycs); + kc->timeout_kycs = json_array (); + GNUNET_assert (NULL != kc->timeout_kycs); + + /* process 'timeout_ms' argument */ + { + const char *long_poll_timeout_s; + + long_poll_timeout_s + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "timeout_ms"); + if (NULL != long_poll_timeout_s) + { + unsigned int timeout_ms; + char dummy; + + if (1 != sscanf (long_poll_timeout_s, + "%u%c", + &timeout_ms, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "timeout_ms must be non-negative number"); + } + kc->timeout = GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_MILLISECONDS, + timeout_ms); + kc->timeout_task + = GNUNET_SCHEDULER_add_delayed (kc->timeout, + &handle_kyc_timeout, + kc); + } + } /* end timeout processing */ + + /* process 'exchange_url' argument */ + kc->exchange_url = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "exchange_url"); + if ( (NULL != kc->exchange_url) && + (! TALER_url_valid_charset (kc->exchange_url) || + ( (0 != strncasecmp (kc->exchange_url, + "http://", + strlen ("http://"))) && + (0 != strncasecmp (kc->exchange_url, + "https://", + strlen ("https://"))) ) ) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "exchange_url must be a valid HTTP(s) URL"); + } + + /* process 'h_wire' argument */ + { + const char *h_wire_s; + + h_wire_s + = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "h_wire"); + if (NULL != h_wire_s) + { + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (h_wire_s, + strlen (h_wire_s), + &kc->h_wire, + sizeof (kc->h_wire))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "h_wire must be Crockford base32 encoded hash"); + } + kc->have_h_wire = true; + } + } /* end of h_wire processing */ + + /* Check our database */ + { + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->account_kyc_get_status (TMH_db->cls, + mi->settings.id, + kc->have_h_wire + ? &kc->h_wire + : NULL, + kc->exchange_url, + &kyc_status_cb, + kc); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "account_kyc_get_status"); + } + } + if (NULL == kc->exchange_pending_head) + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + MHD_suspend_connection (connection); + kc->suspended = GNUNET_YES; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Suspending KYC request handling while checking with the exchange(s)\n"); + return MHD_YES; + } + if (GNUNET_SYSERR == kc->suspended) + return MHD_NO; /* during shutdown, we don't generate any more replies */ + GNUNET_assert (GNUNET_NO == kc->suspended); + if (0 != kc->response_code) + { + /* We are *done* processing the request, just queue the response (!) */ + if (UINT_MAX == kc->response_code) + { + GNUNET_break (0); + return MHD_NO; /* hard error */ + } + return MHD_queue_response (connection, + kc->response_code, + kc->response); + } + /* we should never get here */ GNUNET_break (0); return MHD_NO; } @@ -48,8 +613,8 @@ get_instances_ID_kyc (struct TMH_MerchantInstance *mi, MHD_RESULT TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc) + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi = hc->instance; @@ -61,8 +626,8 @@ TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh, MHD_RESULT TMH_private_get_instances_default_ID_kyc (const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc) + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi; 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 index 58762c86..682f65a6 100644 --- a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h +++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h @@ -28,6 +28,14 @@ /** + * Force all KYC contexts to be resumed as we are about + * to shut down MHD. + */ +void +TMH_force_kyc_resume (void); + + +/** * Change the instance's kyc settings. * This is the handler called using the instance's own kycentication. * |