commit a7cb67227df85d63c9433981358dea406fd0346c
parent 3ecec2cb8a0e7def733025a8b2d08243aa7bff39
Author: Florian Dold <florian@dold.me>
Date: Mon, 21 Oct 2024 15:06:28 +0200
run immediate measures on POSTing AML decision
Diffstat:
5 files changed, 351 insertions(+), 60 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -17,6 +17,7 @@
* @file taler-exchange-httpd_aml-decision.c
* @brief Handle POST request about an AML decision.
* @author Christian Grothoff
+ * @author Florian Dold
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@@ -28,10 +29,127 @@
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_signatures.h"
+#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_aml-decision.h"
+/**
+ * Context used for processing the AML decision request.
+ */
+struct AmlDecisionContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AmlDecisionContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AmlDecisionContext *prev;
+
+ /**
+ * HTTP status code to use with @e response.
+ */
+ unsigned int response_code;
+
+ /**
+ * Response to return, NULL if none yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for async KYC processing.
+ */
+ struct TEH_KycAmlTrigger *kat;
+
+};
+
+/**
+ * Kept in a DLL.
+ */
+static struct AmlDecisionContext *adc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct AmlDecisionContext *adc_tail;
+
+
+void
+TEH_aml_decision_cleanup ()
+{
+ struct AmlDecisionContext *adc;
+
+ while (NULL != (adc = adc_head))
+ {
+ MHD_resume_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (adc_head,
+ adc_tail,
+ adc);
+ }
+}
+
+
+/**
+ * Function called to clean up aml decision context.
+ *
+ * @param[in,out] rc context to clean up
+ */
+static void
+aml_decision_cleaner (struct TEH_RequestContext *rc)
+{
+ struct AmlDecisionContext *adc = rc->rh_ctx;
+
+ if (NULL != adc->kat)
+ {
+ TEH_kyc_finished_cancel (adc->kat);
+ adc->kat = NULL;
+ }
+ if (NULL != adc->response)
+ {
+ MHD_destroy_response (adc->response);
+ adc->response = NULL;
+ }
+ GNUNET_free (adc);
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
+ */
+static void
+aml_trigger_callback (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct AmlDecisionContext *adc = cls;
+
+ adc->kat = NULL;
+ GNUNET_assert (NULL == adc->response);
+ GNUNET_assert (NULL != response);
+ adc->response_code = http_status;
+ adc->response = response;
+ MHD_resume_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (adc_head,
+ adc_tail,
+ adc);
+ TALER_MHD_daemon_trigger ();
+}
+
+
MHD_RESULT
TEH_handler_post_aml_decision (
struct TEH_RequestContext *rc,
@@ -39,6 +157,7 @@ TEH_handler_post_aml_decision (
const json_t *root)
{
struct MHD_Connection *connection = rc->connection;
+ struct AmlDecisionContext *adc = rc->rh_ctx;
const char *justification;
const char *new_measures = NULL;
bool to_investigate;
@@ -48,6 +167,8 @@ TEH_handler_post_aml_decision (
const char *payto_uri = NULL;
struct TALER_PaytoHashP h_payto;
struct TALER_AmlOfficerSignatureP officer_sig;
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
+ MHD_RESULT ret;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string (
@@ -79,6 +200,23 @@ TEH_handler_post_aml_decision (
struct GNUNET_TIME_Timestamp expiration_time;
json_t *jmeasures = NULL;
+ if (NULL == adc)
+ {
+ /* Initialize context */
+ adc = GNUNET_new (struct AmlDecisionContext);
+ adc->rc = rc;
+ rc->rh_ctx = adc;
+ rc->rh_cleaner = aml_decision_cleaner;
+ }
+
+ if (NULL != adc->response)
+ {
+ ret = MHD_queue_response (rc->connection,
+ adc->response_code,
+ adc->response);
+ goto done;
+ }
+
{
enum GNUNET_GenericReturnValue res;
@@ -86,11 +224,15 @@ TEH_handler_post_aml_decision (
root,
spec);
if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
+ {
+ ret = MHD_NO; /* hard failure */
+ goto done;
+ }
if (GNUNET_NO == res)
{
GNUNET_break_op (0);
- return MHD_YES; /* failure */
+ ret = MHD_YES /* failure */;
+ goto done;
}
}
if (NULL != payto_uri)
@@ -104,11 +246,12 @@ TEH_handler_post_aml_decision (
&h_payto2))
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"payto_uri");
+ goto done;
}
}
@@ -126,45 +269,44 @@ TEH_handler_post_aml_decision (
&officer_sig))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
NULL);
+ goto done;
}
+ lrs = TALER_KYCLOGIC_rules_parse (new_rules);
+ if (NULL == lrs)
{
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "legitimization rule malformed");
+ goto done;
+ }
- lrs = TALER_KYCLOGIC_rules_parse (new_rules);
- if (NULL == lrs)
+ expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
+ if (NULL != new_measures)
+ {
+ jmeasures
+ = TALER_KYCLOGIC_get_measures (lrs,
+ new_measures);
+ if (NULL == jmeasures)
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
+ /* Request specified a new_measure for which the given
+ rule set does not work as it does not define the measure */
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "legitimization rule malformed");
+ "new_measures/new_rules");
+ goto done;
}
- expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
- if (NULL != new_measures)
- {
- jmeasures
- = TALER_KYCLOGIC_get_measures (lrs,
- new_measures);
- if (NULL == jmeasures)
- {
- GNUNET_break_op (0);
- /* Request specified a new_measure for which the given
- rule set does not work as it does not define the measure */
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "new_measures/new_rules");
- }
- }
- TALER_KYCLOGIC_rules_free (lrs);
}
{
@@ -195,48 +337,114 @@ TEH_handler_post_aml_decision (
if (qs <= 0)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_aml_decision");
+ goto done;
}
if (invalid_officer)
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
NULL);
+ goto done;
}
if (unknown_account)
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
"h_payto");
+ goto done;
}
if (GNUNET_TIME_timestamp_cmp (last_date,
>=,
decision_time))
{
GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
NULL);
+ goto done;
}
}
- return TALER_MHD_reply_static (
+ /* Run instant measure if necessary */
+ {
+ const struct TALER_KYCLOGIC_Measure *instant_ms;
+ struct MHD_Response *empty_response;
+
+ instant_ms = TALER_KYCLOGIC_get_instant_measure (lrs, new_measures);
+ if (NULL != instant_ms)
+ {
+ /* We have an 'instant' measure which means we must run the
+ AML program immediately instead of waiting for the account owner
+ to select some measure and contribute their KYC data. */
+ json_t *attributes
+ = json_object (); /* instant: empty attributes */
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running instant measure after AML decision\n");
+
+ GNUNET_assert (NULL != attributes);
+ empty_response
+ = MHD_create_response_from_buffer_static (0,
+ "");
+ GNUNET_assert (NULL != empty_response);
+ adc->kat
+ = TEH_kyc_finished2 (
+ &rc->async_scope_id,
+ 0LL,
+ instant_ms,
+ &h_payto,
+ "SKIP", /* provider */
+ NULL,
+ NULL,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ attributes,
+ MHD_HTTP_NO_CONTENT, /* http status */
+ empty_response, /* MHD_Response */
+ &aml_trigger_callback,
+ adc);
+ json_decref (attributes);
+ if (NULL == adc->kat)
+ {
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
+ "TEH_kyc_finished");
+ goto done;
+ }
+
+ MHD_suspend_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_insert (adc_head,
+ adc_tail,
+ adc);
+ ret = MHD_YES;
+ goto done;
+ }
+ }
+ ret = TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
+ goto done;
+
+done:
+ TALER_KYCLOGIC_rules_free (lrs);
+ return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c
@@ -477,33 +477,7 @@ add_kyc_history_entry (
}
-/**
- * We have finished a KYC process and obtained new
- * @a attributes for a given @a account_id.
- * Check with the KYC-AML trigger to see if we need
- * to initiate an AML process, and store the attributes
- * in the database. Then call @a cb.
- *
- * @param scope the HTTP request logging scope
- * @param process_row legitimization process the data provided is about,
- * or must be 0 if instant_ms is given
- * @param instant_ms instant measure to run, used if @a process_row is 0,
- * otherwise must be NULL
- * @param account_id account the webhook was about
- * @param provider_name name of the provider with the logic that was run
- * @param provider_user_id set to user ID at the provider, or
- * NULL if not supported or unknown
- * @param provider_legitimization_id set to legitimization process ID at the provider,
- * or NULL if not supported or unknown
- * @param expiration until when is the KYC check valid
- * @param attributes user attributes returned by the provider
- * @param http_status HTTP status code of @a response
- * @param[in] response to return to the HTTP client, can be NULL
- * @param cb function to call with the result
- * @param cb_cls closure for @a cb
- * @return handle to cancel the operation
- */
-static struct TEH_KycAmlTrigger *
+struct TEH_KycAmlTrigger *
TEH_kyc_finished2 (
const struct GNUNET_AsyncScopeId *scope,
uint64_t process_row,
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.h b/src/exchange/taler-exchange-httpd_common_kyc.h
@@ -87,6 +87,48 @@ TEH_kyc_finished (
TEH_KycAmlTriggerCallback cb,
void *cb_cls);
+/**
+ * We have finished a KYC process and obtained new
+ * @a attributes for a given @a account_id.
+ * Check with the KYC-AML trigger to see if we need
+ * to initiate an AML process, and store the attributes
+ * in the database. Then call @a cb.
+ *
+ * @param scope the HTTP request logging scope
+ * @param process_row legitimization process the data provided is about,
+ * or must be 0 if instant_ms is given
+ * @param instant_ms instant measure to run, used if @a process_row is 0,
+ * otherwise must be NULL
+ * @param account_id account the webhook was about
+ * @param provider_name name of the provider with the logic that was run
+ * @param provider_user_id set to user ID at the provider, or
+ * NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider,
+ * or NULL if not supported or unknown
+ * @param expiration until when is the KYC check valid
+ * @param attributes user attributes returned by the provider
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client, can be NULL
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel the operation
+ */
+struct TEH_KycAmlTrigger *
+TEH_kyc_finished2 (
+ const struct GNUNET_AsyncScopeId *scope,
+ uint64_t process_row,
+ const struct TALER_KYCLOGIC_Measure *instant_ms,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_name,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
+ unsigned int http_status,
+ struct MHD_Response *response,
+ TEH_KycAmlTriggerCallback cb,
+ void *cb_cls);
+
/**
* Cancel KYC finish operation.
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
@@ -772,6 +772,22 @@ TALER_KYCLOGIC_rule_get_instant_measure (
/**
+ * Check if there is a measure in @a lrs
+ * that is included in @a measure_spec
+ * and a SKIP measure, and thus should be immediately
+ * executed.
+ *
+ * @param rls legitimization rule set
+ * @param measures_spec measures spec
+ * @returns NULL if there is no instant measure
+ */
+const struct TALER_KYCLOGIC_Measure *
+TALER_KYCLOGIC_get_instant_measure (
+ const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
+ const char *measures_spec);
+
+
+/**
* Handle to manage a running AML program.
*/
struct TALER_KYCLOGIC_AmlProgramRunnerHandle;
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -1172,6 +1172,57 @@ TALER_KYCLOGIC_voluntary_measures (
}
+const struct TALER_KYCLOGIC_Measure *
+TALER_KYCLOGIC_get_instant_measure (
+ const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
+ const char *measures_spec)
+{
+ char *nm;
+ const struct TALER_KYCLOGIC_Measure *ret = NULL;
+
+ if ('+' == measures_spec[0])
+ {
+ nm = GNUNET_strdup (&measures_spec[1]);
+ }
+ else
+ {
+ nm = GNUNET_strdup (measures_spec);
+ }
+ for (const char *tok = strtok (nm, " ");
+ NULL != tok;
+ tok = strtok (NULL, " "))
+ {
+ const struct TALER_KYCLOGIC_Measure *ms;
+
+ if (0 == strcasecmp ("verboten",
+ tok))
+ {
+ continue;
+ }
+ ms = find_measure (lrs,
+ tok);
+ if (NULL == ms)
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ if (0 == strcasecmp ("verboten",
+ ms->check_name))
+ {
+ continue;
+ }
+ if (0 == strcasecmp ("SKIP", ms->check_name))
+ {
+ ret = ms;
+ goto done;
+ }
+ }
+done:
+ GNUNET_free (nm);
+ return ret;
+}
+
+
json_t *
TALER_KYCLOGIC_get_measures (
const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,