/*
This file is part of GNU Taler
(C) 2021-2024 Taler Systems SA
GNU Taler is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation; either version 3,
or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with TALER; see the file COPYING. If not,
see
*/
/**
* @file taler-merchant-httpd_private-get-instances-ID-kyc.c
* @brief implementing GET /instances/$ID/kyc request handling
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_exchanges.h"
#include
#include
#include
/**
* 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
/**
* How long should clients cache a KYC failure response?
*/
#define EXPIRATION_KYC_FAILURE GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 5)
/**
* How long should clients cache a KYC success response?
*/
#define EXPIRATION_KYC_SUCCESS GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_HOURS, 1)
/**
* 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;
/**
* Find operation where we connect to the respective exchange.
*/
struct TMH_EXCHANGES_KeysOperation *fo;
/**
* JSON array of payto-URIs with KYC auth wire transfer
* instructions. Provided if @e auth_ok is false and
* @e kyc_auth_conflict is false.
*/
json_t *pkaa;
/**
* The keys of the exchange.
*/
struct TALER_EXCHANGE_Keys *keys;
/**
* KYC request this exchange request is made for.
*/
struct KycContext *kc;
/**
* JSON array of AccountLimits that apply, NULL if
* unknown (and likely defaults apply).
*/
json_t *jlimits;
/**
* Our account's payto URI.
*/
struct TALER_FullPayto payto_uri;
/**
* Base URL of the exchange.
*/
char *exchange_url;
/**
* Hash of the wire account (with salt) we are checking.
*/
struct TALER_MerchantWireHashP h_wire;
/**
* Current access token for the KYC SPA. Only set
* if @e auth_ok is true.
*/
struct TALER_AccountAccessTokenP access_token;
/**
* Timestamp when we last got a reply from the exchange.
*/
struct GNUNET_TIME_Timestamp last_check;
/**
* Last HTTP status code obtained via /kyc-check from
* the exchange.
*/
unsigned int last_http_status;
/**
* Last Taler error code returned from /kyc-check.
*/
enum TALER_ErrorCode last_ec;
/**
* True if this account
* cannot work at this exchange because KYC auth is
* impossible.
*/
bool kyc_auth_conflict;
/**
* We could not get /keys from the exchange.
*/
bool no_keys;
/**
* True if @e access_token is available.
*/
bool auth_ok;
/**
* True if we believe no KYC is currently required
* for this account at this exchange.
*/
bool kyc_ok;
/**
* True if the exchange exposed to us that the account
* is currently under AML review.
*/
bool in_aml_review;
};
/**
* 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;
/**
* 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 *kycs_data;
/**
* 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;
/**
* Notification handler from database on changes
* to the KYC status.
*/
struct GNUNET_DB_EventHandler *eh;
/**
* Set to the h_wire of the merchant account if
* @a have_h_wire is true, used to filter by account.
*/
struct TALER_MerchantWireHashP h_wire;
/**
* How long are we willing to wait for the exchange(s)?
*/
struct GNUNET_TIME_Absolute 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;
/**
* What state are we long-polling for?
*/
enum TALER_EXCHANGE_KycLongPollTarget lpt;
/**
* True if @e h_wire was given.
*/
bool have_h_wire;
/**
* We're still waiting on the exchange to determine
* the KYC status of our deposit(s).
*/
bool return_immediately;
};
/**
* 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 (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);
if (NULL != ekr->fo)
{
TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
ekr->fo = NULL;
}
json_decref (ekr->pkaa);
json_decref (ekr->jlimits);
if (NULL != ekr->keys)
TALER_EXCHANGE_keys_decref (ekr->keys);
GNUNET_free (ekr->exchange_url);
GNUNET_free (ekr->payto_uri.full_payto);
GNUNET_free (ekr);
}
if (NULL != kc->eh)
{
TMH_db->event_listen_cancel (kc->eh);
kc->eh = 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->kycs_data);
GNUNET_free (kc);
}
/**
* Resume the given KYC context and send the final response. Stores the
* response in the @a kc and signals MHD to resume the connection. Also
* ensures MHD runs immediately.
*
* @param kc KYC context
*/
static void
resume_kyc_with_response (struct KycContext *kc)
{
char dat[128];
kc->response_code = MHD_HTTP_OK;
kc->response = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_incref ("kyc_data",
kc->kycs_data));
/* KYC failed, cache briefly */
TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute (
EXPIRATION_KYC_FAILURE),
dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (kc->response,
MHD_HTTP_HEADER_EXPIRES,
dat));
GNUNET_break (MHD_YES ==
MHD_add_response_header (kc->response,
MHD_HTTP_HEADER_CACHE_CONTROL,
"max-age=300"));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming /kyc handling as exchange interaction is done (%u)\n",
MHD_HTTP_OK);
if (GNUNET_YES == kc->suspended)
{
kc->suspended = GNUNET_NO;
MHD_resume_connection (kc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
}
/**
* Handle a DB event about an update relevant
* for the processing of the kyc request.
*
* @param cls our `struct KycContext`
* @param extra additional event data provided
* @param extra_size number of bytes in @a extra
*/
static void
kyc_change_cb (void *cls,
const void *extra,
size_t extra_size)
{
struct KycContext *kc = cls;
GNUNET_assert (GNUNET_YES == kc->suspended);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming KYC with gateway timeout\n");
kc->suspended = GNUNET_NO;
MHD_resume_connection (kc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* 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.
*
* @param[in,out] ekr overall request context
*/
static json_t *
get_exchange_limits (
struct ExchangeKycRequest *ekr)
{
const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
json_t *limits;
if (NULL != ekr->jlimits)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning custom KYC limits\n");
return json_incref (ekr->jlimits);
}
if (NULL == keys)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No keys, thus no default KYC limits known\n");
return NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning default KYC limits (%u/%u)\n",
keys->hard_limits_length,
keys->zero_limits_length);
limits = json_array ();
GNUNET_assert (NULL != limits);
for (unsigned int i = 0; ihard_limits_length; i++)
{
const struct TALER_EXCHANGE_AccountLimit *limit
= &keys->hard_limits[i];
pack_limit (limit,
limits);
}
for (unsigned int i = 0; izero_limits_length; i++)
{
const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit
= &keys->zero_limits[i];
json_t *jl;
struct TALER_Amount zero;
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (keys->currency,
&zero));
jl = GNUNET_JSON_PACK (
TALER_JSON_pack_kycte ("operation_type",
zlimit->operation_type),
GNUNET_JSON_pack_time_rel ("timeframe",
GNUNET_TIME_UNIT_ZERO),
TALER_JSON_pack_amount ("threshold",
&zero),
GNUNET_JSON_pack_bool ("soft_limit",
true)
);
GNUNET_assert (0 ==
json_array_append_new (limits,
jl));
}
return limits;
}
/**
* Maps @a ekr to a status code for clients to interpret the
* overall result.
*
* @param ekr request summary
* @return status of the KYC state as a string
*/
static const char *
map_to_status (const struct ExchangeKycRequest *ekr)
{
if (ekr->no_keys)
{
return "no-exchange-keys";
}
if (ekr->kyc_ok)
{
return "ready";
}
if (! ekr->auth_ok)
{
if (ekr->kyc_auth_conflict)
return "kyc-wire-impossible";
return "kyc-wire-required";
}
if (ekr->in_aml_review)
return "awaiting-aml-review";
switch (ekr->last_http_status)
{
case 0:
return "exchange-unreachable";
case MHD_HTTP_OK:
/* then we should have kyc_ok */
GNUNET_break (0);
return NULL;
case MHD_HTTP_ACCEPTED:
/* Then KYC is really what is needed */
return "kyc-required";
case MHD_HTTP_NO_CONTENT:
/* then we should have had kyc_ok! */
GNUNET_break (0);
return NULL;
case MHD_HTTP_FORBIDDEN:
/* then we should have had ! auth_ok */
GNUNET_break (0);
return NULL;
case MHD_HTTP_NOT_FOUND:
/* then we should have had ! auth_ok */
GNUNET_break (0);
return NULL;
case MHD_HTTP_CONFLICT:
/* then we should have had ! auth_ok */
GNUNET_break (0);
return NULL;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
return "exchange-internal-error";
case MHD_HTTP_GATEWAY_TIMEOUT:
return "exchange-gateway-timeout";
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Exchange responded with unexpected HTTP status %u to /kyc-check request!\n",
ekr->last_http_status);
break;
}
return "exchange-status-invalid";
}
/**
* Take data from @a ekr to expand our response.
*
* @param ekr exchange we are done inspecting
*/
static void
ekr_expand_response (struct ExchangeKycRequest *ekr)
{
const char *status;
status = map_to_status (ekr);
if (NULL == status)
{
GNUNET_break (0);
status = "logic-bug";
}
GNUNET_assert (
0 ==
json_array_append_new (
ekr->kc->kycs_data,
GNUNET_JSON_PACK (
TALER_JSON_pack_full_payto (
"payto_uri",
ekr->payto_uri),
GNUNET_JSON_pack_data_auto (
"h_wire",
&ekr->h_wire),
GNUNET_JSON_pack_string (
"status",
status),
GNUNET_JSON_pack_string (
"exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_bool ("no_keys",
ekr->no_keys),
GNUNET_JSON_pack_bool ("auth_conflict",
ekr->kyc_auth_conflict),
GNUNET_JSON_pack_uint64 ("exchange_http_status",
ekr->last_http_status),
(TALER_EC_NONE == ekr->last_ec)
? GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
"dummy",
NULL))
: GNUNET_JSON_pack_uint64 ("exchange_code",
ekr->last_ec),
ekr->auth_ok
? GNUNET_JSON_pack_data_auto (
"access_token",
&ekr->access_token)
: GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
"dummy",
NULL)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_steal (
"limits",
get_exchange_limits (ekr))),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_incref ("payto_kycauths",
ekr->pkaa))
)));
}
/**
* We are done with the KYC request @a ekr. Remove it from the work list and
* check if we are done overall.
*
* @param[in] ekr key request that is done (and will be freed)
*/
static void
ekr_finished (struct ExchangeKycRequest *ekr)
{
struct KycContext *kc = ekr->kc;
ekr_expand_response (ekr);
GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
json_decref (ekr->jlimits);
json_decref (ekr->pkaa);
if (NULL != ekr->keys)
TALER_EXCHANGE_keys_decref (ekr->keys);
GNUNET_free (ekr->exchange_url);
GNUNET_free (ekr->payto_uri.full_payto);
GNUNET_free (ekr);
if (NULL != kc->exchange_pending_head)
return; /* wait for more */
if ( (! kc->return_immediately) &&
(! GNUNET_TIME_absolute_is_past (kc->timeout)) )
{
if (GNUNET_NO == kc->suspended)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending: long poll target %d not reached\n",
kc->lpt);
MHD_suspend_connection (kc->connection);
kc->suspended = GNUNET_YES;
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Remaining suspended: long poll target %d not reached\n",
kc->lpt);
}
return;
}
/* All exchange requests done, create final
big response from cumulated replies */
resume_kyc_with_response (kc);
}
/**
* Figure out which exchange accounts from @a keys could
* be used for a KYC auth wire transfer from the account
* that @a ekr is checking. Will set the "pkaa" array
* in @a ekr.
*
* @param[in,out] ekr request we are processing
*/
static void
determine_eligible_accounts (
struct ExchangeKycRequest *ekr)
{
struct KycContext *kc = ekr->kc;
const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
struct TALER_Amount kyc_amount;
char *merchant_pub_str;
struct TALER_NormalizedPayto np;
ekr->pkaa = json_array ();
GNUNET_assert (NULL != ekr->pkaa);
{
const struct TALER_EXCHANGE_GlobalFee *gf;
gf = TALER_EXCHANGE_get_global_fee (keys,
GNUNET_TIME_timestamp_get ());
if (NULL == gf)
{
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (keys->currency,
&kyc_amount));
}
else
{
/* FIXME: history fee should be globally renamed to KYC fee... */
kyc_amount = gf->fees.history;
}
}
merchant_pub_str
= GNUNET_STRINGS_data_to_string_alloc (
&kc->mi->merchant_pub,
sizeof (kc->mi->merchant_pub));
/* For all accounts of the exchange */
np = TALER_payto_normalize (ekr->payto_uri);
for (unsigned int i = 0; iaccounts_len; i++)
{
const struct TALER_EXCHANGE_WireAccount *account
= &keys->accounts[i];
/* KYC auth transfers are never supported with conversion */
if (NULL != account->conversion_url)
continue;
/* filter by source account by credit_restrictions */
if (GNUNET_YES !=
TALER_EXCHANGE_test_account_allowed (account,
true, /* credit */
np))
continue;
/* exchange account is allowed, add it */
{
const char *exchange_account_payto
= account->fpayto_uri.full_payto;
char *payto_kycauth;
if (TALER_amount_is_zero (&kyc_amount))
GNUNET_asprintf (&payto_kycauth,
"%s%cmessage=KYC:%s",
exchange_account_payto,
(NULL == strchr (exchange_account_payto,
'?'))
? '?'
: '&',
merchant_pub_str);
else
GNUNET_asprintf (&payto_kycauth,
"%s%camount=%s&message=KYC:%s",
exchange_account_payto,
(NULL == strchr (exchange_account_payto,
'?'))
? '?'
: '&',
TALER_amount2s (&kyc_amount),
merchant_pub_str);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found account %s where KYC auth is possible\n",
payto_kycauth);
GNUNET_assert (0 ==
json_array_append_new (ekr->pkaa,
json_string (payto_kycauth)));
GNUNET_free (payto_kycauth);
}
}
GNUNET_free (np.normalized_payto);
GNUNET_free (merchant_pub_str);
}
/**
* Function called with the result of a #TMH_EXCHANGES_keys4exchange()
* operation. Runs the KYC check against the exchange.
*
* @param cls closure with our `struct ExchangeKycRequest *`
* @param keys keys of the exchange context
* @param exchange representation of the exchange
*/
static void
kyc_with_exchange (void *cls,
struct TALER_EXCHANGE_Keys *keys,
struct TMH_Exchange *exchange)
{
struct ExchangeKycRequest *ekr = cls;
(void) exchange;
ekr->fo = NULL;
if (NULL == keys)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to download `%skeys`\n",
ekr->exchange_url);
ekr->no_keys = true;
ekr_finished (ekr);
return;
}
ekr->keys = TALER_EXCHANGE_keys_incref (keys);
if (! ekr->auth_ok)
{
determine_eligible_accounts (ekr);
if (0 == json_array_size (ekr->pkaa))
{
/* No KYC auth wire transfers are possible to this exchange from
our merchant bank account, so we cannot use this account with
this exchange if it has any KYC requirements! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC auth to `%s' impossible for merchant account `%s'\n",
ekr->exchange_url,
ekr->payto_uri.full_payto);
ekr->kyc_auth_conflict = true;
}
}
ekr_finished (ekr);
}
/**
* 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 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
* @param access_token access token for the KYC SPA, NULL if we cannot access it yet (need KYC auth wire transfer)
* @param last_http_status last HTTP status from /kyc-check
* @param last_ec last Taler error code from /kyc-check
* @param in_aml_review true if the account is pending review
* @param jlimits JSON array of applicable AccountLimits, or NULL if unknown (like defaults apply)
*/
static void
kyc_status_cb (
void *cls,
const struct TALER_MerchantWireHashP *h_wire,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
bool kyc_ok,
const struct TALER_AccountAccessTokenP *access_token,
unsigned int last_http_status,
enum TALER_ErrorCode last_ec,
bool in_aml_review,
const json_t *jlimits)
{
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC status for `%s' at `%s' is %u/%s/%s/%s\n",
payto_uri.full_payto,
exchange_url,
last_http_status,
kyc_ok ? "KYC OK" : "KYC NEEDED",
in_aml_review ? "IN AML REVIEW" : "NO AML REVIEW",
NULL == jlimits ? "DEFAULT LIMITS" : "CUSTOM LIMITS");
switch (kc->lpt)
{
case TALER_EXCHANGE_KLPT_NONE:
break;
case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
if (NULL != access_token)
kc->return_immediately = true;
break;
case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
if (! in_aml_review)
kc->return_immediately = true;
break;
case TALER_EXCHANGE_KLPT_KYC_OK:
if (kyc_ok)
kc->return_immediately = true;
break;
}
ekr = GNUNET_new (struct ExchangeKycRequest);
GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
ekr->last_http_status = last_http_status;
ekr->last_ec = last_ec;
if (NULL != jlimits)
ekr->jlimits = json_incref ((json_t *) jlimits);
ekr->h_wire = *h_wire;
ekr->exchange_url = GNUNET_strdup (exchange_url);
ekr->payto_uri.full_payto
= GNUNET_strdup (payto_uri.full_payto);
ekr->last_check = last_check;
ekr->kyc_ok = kyc_ok;
ekr->kc = kc;
ekr->in_aml_review = in_aml_review;
ekr->auth_ok = (NULL != access_token);
if ( (! ekr->auth_ok) ||
(NULL == ekr->jlimits) )
{
/* Figure out wire transfer instructions */
if (GNUNET_NO == kc->suspended)
{
MHD_suspend_connection (kc->connection);
kc->suspended = GNUNET_YES;
}
ekr->fo = TMH_EXCHANGES_keys4exchange (
exchange_url,
false,
&kyc_with_exchange,
ekr);
if (NULL == ekr->fo)
{
GNUNET_break (0);
ekr_finished (ekr);
return;
}
return;
}
ekr->access_token = *access_token;
ekr_finished (ekr);
}
/**
* Check the KYC status of an instance.
*
* @param mi instance to check KYC status of
* @param connection the MHD connection to handle
* @param[in,out] hc context with further information about the request
* @return MHD result code
*/
static MHD_RESULT
get_instances_ID_kyc (
struct TMH_MerchantInstance *mi,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct KycContext *kc = hc->ctx;
if (NULL == kc)
{
kc = GNUNET_new (struct KycContext);
kc->mi = mi;
hc->ctx = kc;
hc->cc = &kyc_context_cleanup;
GNUNET_CONTAINER_DLL_insert (kc_head,
kc_tail,
kc);
kc->connection = connection;
kc->hc = hc;
kc->kycs_data = json_array ();
GNUNET_assert (NULL != kc->kycs_data);
TALER_MHD_parse_request_timeout (connection,
&kc->timeout);
{
uint64_t num = 0;
int val;
TALER_MHD_parse_request_number (connection,
"lpt",
&num);
val = (int) num;
if ( (val < 0) ||
(val > TALER_EXCHANGE_KLPT_MAX) )
{
/* Protocol violation, but we can be graceful and
just ignore the long polling! */
GNUNET_break_op (0);
val = TALER_EXCHANGE_KLPT_NONE;
}
kc->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
}
kc->return_immediately
= (TALER_EXCHANGE_KLPT_NONE == kc->lpt);
/* 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)) ||
(! TALER_is_web_url (kc->exchange_url)) ) )
{
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");
}
TALER_MHD_parse_request_arg_auto (connection,
"h_wire",
&kc->h_wire,
kc->have_h_wire);
if ( (TALER_EXCHANGE_KLPT_NONE != kc->lpt) &&
(! GNUNET_TIME_absolute_is_past (kc->timeout)) )
{
if (kc->have_h_wire)
{
struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
.header.size = htons (sizeof (ev)),
.header.type = htons (
TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED
),
.h_wire = kc->h_wire
};
kc->eh = TMH_db->event_listen (
TMH_db->cls,
&ev.header,
GNUNET_TIME_absolute_get_remaining (kc->timeout),
&kyc_change_cb,
kc);
}
else
{
struct GNUNET_DB_EventHeaderP hdr = {
.size = htons (sizeof (hdr)),
.type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED)
};
kc->eh = TMH_db->event_listen (
TMH_db->cls,
&hdr,
GNUNET_TIME_absolute_get_remaining (kc->timeout),
&kyc_change_cb,
kc);
}
} /* end register LISTEN hooks */
} /* end 1st time initialization */
if (GNUNET_SYSERR == kc->suspended)
return MHD_NO; /* during shutdown, we don't generate any more replies */
GNUNET_assert (GNUNET_NO == kc->suspended);
if (NULL != kc->response)
return MHD_queue_response (connection,
kc->response_code,
kc->response);
/* Check our database */
{
enum GNUNET_DB_QueryStatus qs;
GNUNET_break (0 ==
json_array_clear (kc->kycs_data));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Checking KYC status for %s (%d/%s)\n",
mi->settings.id,
kc->have_h_wire,
kc->exchange_url);
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);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"account_kyc_get_status returned %d records\n",
(int) qs);
if (qs < 0)
{
/* Database error */
GNUNET_break (0);
if (GNUNET_YES == kc->suspended)
{
/* must have suspended before DB error, resume! */
MHD_resume_connection (connection);
kc->suspended = GNUNET_NO;
}
return TALER_MHD_reply_with_ec (
connection,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"account_kyc_get_status");
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* no matching accounts, could not have suspended */
GNUNET_assert (GNUNET_NO == kc->suspended);
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
}
if (GNUNET_YES == kc->suspended)
return MHD_YES;
/* Should have generated a response */
GNUNET_break (NULL != kc->response);
return MHD_queue_response (connection,
kc->response_code,
kc->response);
}
MHD_RESULT
TMH_private_get_instances_ID_kyc (
const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
(void) rh;
return get_instances_ID_kyc (mi,
connection,
hc);
}
MHD_RESULT
TMH_private_get_instances_default_ID_kyc (
const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi;
(void) rh;
mi = TMH_lookup_instance (hc->infix);
if (NULL == mi)
{
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
hc->infix);
}
return get_instances_ID_kyc (mi,
connection,
hc);
}
/* end of taler-merchant-httpd_private-get-instances-ID-kyc.c */