summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-10-10 14:39:24 +0200
committerChristian Grothoff <christian@grothoff.org>2021-10-10 14:39:24 +0200
commite668bcdeb5317f99f39957425ce735139c01810d (patch)
treeab6970df9a6679609019d253a75a27437f24b57d
parente3fcff1c567abf103b788bfe7fcc666ec82533d9 (diff)
downloadmerchant-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.c1
-rw-r--r--src/backend/taler-merchant-httpd.h2
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c36
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c573
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.h8
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.
*