diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c | 573 |
1 files changed, 569 insertions, 4 deletions
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; |