commit a8d37edac35b6002d0fe5d366ce2f4fb0feaeeef
parent a5c5f597d87fed28cfc49369b3bf7e98f6eacf8c
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 4 Dec 2024 22:24:52 +0100
more KYC logic refactoring
Diffstat:
4 files changed, 235 insertions(+), 200 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c
@@ -849,6 +849,12 @@ struct TEH_LegitimizationCheckHandle
struct TEH_KycMeasureRunContext *kat;
/**
+ * Handle for the task that gets us the latest
+ * applicable rules.
+ */
+ struct TALER_EXCHANGEDB_RuleUpdater *ru;
+
+ /**
* Payto-URI of the account.
*/
struct TALER_FullPayto payto_uri;
@@ -922,6 +928,15 @@ struct TEH_LegitimizationCheckHandle
*/
bool have_merchant_pub;
+ /**
+ * Set to true if the merchant public key does not
+ * match the public key we have on file for this
+ * target account *and* a rule actually triggered
+ * for this operation (and thus a new KYC AUTH is
+ * required).
+ */
+ bool bad_kyc_auth;
+
};
@@ -1208,6 +1223,7 @@ amount_iterator_wrapper_cb (
so we indeed have a problem! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC: Mismatch between merchant_pub and target_pub is relevant!\n");
+ lch->bad_kyc_auth = true;
}
return lch->ai (lch->ai_cls,
limit,
@@ -1216,212 +1232,36 @@ amount_iterator_wrapper_cb (
}
+/**
+ * Function called with the current rule set.
+ *
+ * @param cls a `struct TEH_LegitimizationCheckHandle *`
+ * @param legitimization_outcome_last_row row the rule set is based on
+ * @param rur includes legitimziation rule set that applies to the account
+ * (owned by callee, callee must free the lrs!)
+ */
static void
-legitimization_check_run (
- struct TEH_LegitimizationCheckHandle *lch)
+current_rules_cb (
+ void *cls,
+ struct TALER_EXCHANGEDB_RuleUpdaterResult *rur)
{
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
- const struct TALER_KYCLOGIC_KycRule *requirement;
+ struct TEH_LegitimizationCheckHandle *lch = cls;
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = rur->lrs;
+ struct GNUNET_AsyncScopeSave old_scope;
enum GNUNET_DB_QueryStatus qs;
+ const struct TALER_KYCLOGIC_KycRule *requirement;
const struct TALER_KYCLOGIC_Measure *instant_ms;
- struct GNUNET_AsyncScopeSave old_scope;
- if (! TEH_enable_kyc)
- {
- /* AML/KYC disabled, just immediately return success! */
- lch->lcr.kyc.requirement_row = 0;
- lch->lcr.kyc.ok = true;
- lch->lcr.bad_kyc_auth = false;
- lch->lcr.expiration_date
- = GNUNET_TIME_UNIT_FOREVER_TS;
- memset (&lch->lcr.next_threshold,
- 0,
- sizeof (struct TALER_Amount));
- lch->lcr.http_status = 0;
- lch->lcr.response = NULL;
- lch->async_task
- = GNUNET_SCHEDULER_add_now (
- &async_return_legi_result,
- lch);
- return;
- }
GNUNET_async_scope_enter (&lch->scope,
&old_scope);
+ if (TALER_EC_NONE != rur->ec)
{
- json_t *jrules;
- bool no_account_pub;
- bool no_reserve_pub;
-
- qs = TEH_plugin->get_kyc_rules (
- TEH_plugin->cls,
- &lch->h_payto,
- &no_account_pub,
- &lch->lcr.kyc.account_pub,
- &no_reserve_pub,
- &lch->lcr.reserve_pub.reserve_pub,
- &jrules);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- legi_fail (lch,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_kyc_rules");
- GNUNET_async_scope_restore (&old_scope);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "get_kyc_rules returned %d/%d/%d/%d\n",
- (int) qs,
- ! no_account_pub,
- ! no_reserve_pub,
- NULL != jrules);
-
- lch->lcr.kyc.have_account_pub
- = ! no_account_pub;
- lch->lcr.have_reserve_pub
- = ! no_reserve_pub;
- if ( (lch->have_merchant_pub) &&
- ( (! lch->lcr.kyc.have_account_pub) ||
- (0 !=
- GNUNET_memcmp (&lch->merchant_pub,
- &lch->lcr.kyc.account_pub.merchant_pub)) ) &&
- ( (! lch->lcr.have_reserve_pub) ||
- (0 !=
- GNUNET_memcmp (&lch->merchant_pub,
- &lch->lcr.reserve_pub.merchant_pub)) ) )
- {
- if (NULL == jrules)
- {
- /* We do not have custom rules, defer enforcing merchant_pub
- match until we actually have deposit constraints */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC: merchant_pub given but no known target_pub(%d)/reserve_pub(%d) match (%d)!\n",
- lch->lcr.kyc.have_account_pub,
- lch->lcr.have_reserve_pub,
- (int) qs);
- lch->lcr.bad_kyc_auth = true;
- }
- else
- {
- /* We have custom rules, but the target_pub for
- those custom rules does not match the
- merchant_pub. Fail the KYC process! */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC: merchant_pub does not match target_pub of custom rules!\n");
- json_decref (jrules);
- fail_kyc_auth (lch);
- goto cleanup;
- }
- }
-
- /* parse and free jrules (if we had any) */
- if (NULL != jrules)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC: have custom KYC rules for this account!\n");
- lrs = TALER_KYCLOGIC_rules_parse (jrules);
- GNUNET_break (NULL != lrs);
- /* Fall back to default rules on parse error! */
- json_decref (jrules);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC: default KYC rules apply to this account!\n");
- }
- }
-
- /* FIXME(fdold, 2024-11-08): We are doing the same logic
- here and in kyc-info, abstract it out? */
- /* FIXME(cg-2024-12-02): Also some duplication with
- code around run_measure in taler-exchange-aggregator! */
-
- /* Check if ruleset is expired and we need to run the successor measure */
- if (NULL != lrs)
- {
- struct GNUNET_TIME_Timestamp ts;
-
- ts = TALER_KYCLOGIC_rules_get_expiration (lrs);
- if (GNUNET_TIME_absolute_is_past (ts.abs_time))
- {
- const struct TALER_KYCLOGIC_Measure *successor_measure;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Current KYC ruleset expired, running successor measure.\n");
-
- successor_measure = TALER_KYCLOGIC_rules_get_successor (lrs);
- if (NULL == successor_measure)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Successor measure unknown, falling back to default rules!\n");
- TALER_KYCLOGIC_rules_free (lrs);
- lrs = NULL;
- }
- else if (0 == strcmp (successor_measure->prog_name, "SKIP"))
- {
- lch->measure_run_ctx = TEH_kyc_run_measure_directly (
- &lch->scope,
- successor_measure,
- &lch->h_payto,
- &legi_check_aml_trigger_cb,
- lch);
- if (NULL == lch->measure_run_ctx)
- {
- legi_fail (lch,
- TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE,
- "successor measure");
- }
- goto cleanup;
- }
- else
- {
- bool unknown_account;
- struct GNUNET_TIME_Timestamp decision_time
- = GNUNET_TIME_timestamp_get ();
- struct GNUNET_TIME_Timestamp last_date;
- json_t *succ_jmeasures = TALER_KYCLOGIC_get_jmeasures (
- lrs,
- successor_measure->measure_name);
-
- GNUNET_assert (NULL != succ_jmeasures);
- qs = TEH_plugin->insert_successor_measure (
- TEH_plugin->cls,
- &lch->h_payto,
- decision_time,
- successor_measure->measure_name,
- succ_jmeasures,
- &unknown_account,
- &last_date);
- json_decref (succ_jmeasures);
- if (qs <= 0)
- {
- legi_fail (lch,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_successor_measure");
- goto cleanup;
- }
- if (unknown_account)
- {
- legi_fail (lch,
- TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
- NULL);
- goto cleanup;
- }
- /* We tolerate conflicting decision times for automatic decisions. */
- GNUNET_break (
- GNUNET_TIME_timestamp_cmp (last_date,
- >=,
- decision_time));
- /* Back to default rules. */
- TALER_KYCLOGIC_rules_free (lrs);
- lrs = NULL;
- }
- }
+ /* rollback should not be needed, but better be safe */
+ TEH_plugin->rollback (TEH_plugin->cls);
+ legi_fail (lch,
+ rur->ec,
+ rur->hint);
+ goto cleanup;
}
qs = TALER_KYCLOGIC_kyc_test_required (
@@ -1434,13 +1274,25 @@ legitimization_check_run (
if (qs < 0)
{
GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
legi_fail (lch,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
goto cleanup;
}
- if (lch->lcr.bad_kyc_auth)
+ // FIXME: check that this change is a valid correction,
+ // used to be just lch->lcr.bad_key_auth, but that
+ // would be independent of there being an applicable KYC rule!
+ if (lch->bad_kyc_auth)
{
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (0 > qs)
+ {
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc_test_required");
+ goto cleanup;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC auth required\n");
fail_kyc_auth (lch);
@@ -1449,6 +1301,14 @@ legitimization_check_run (
if (NULL == requirement)
{
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (0 > qs)
+ {
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc_test_required");
+ goto cleanup;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check passed\n");
lch->lcr.kyc.ok = true;
@@ -1468,6 +1328,8 @@ legitimization_check_run (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirement is %s\n",
TALER_KYCLOGIC_rule2s (requirement));
+ // FIXME: this is again logic that should probably be
+ // shared!
instant_ms
= TALER_KYCLOGIC_rule_get_instant_measure (
requirement);
@@ -1487,10 +1349,19 @@ legitimization_check_run (
if (NULL == lch->kat)
{
GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
legi_fail (lch,
TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE,
NULL);
}
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (0 > qs)
+ {
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc_test_required");
+ goto cleanup;
+ }
goto cleanup;
}
@@ -1524,6 +1395,7 @@ legitimization_check_run (
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
legi_fail (lch,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"trigger_kyc_rule_for_account");
@@ -1532,11 +1404,20 @@ legitimization_check_run (
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
legi_fail (lch,
TALER_EC_GENERIC_DB_STORE_FAILED,
"trigger_kyc_rule_for_account");
goto cleanup;
}
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (0 > qs)
+ {
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc_test_required");
+ goto cleanup;
+ }
/* return success! */
lch->async_task
= GNUNET_SCHEDULER_add_now (
@@ -1548,6 +1429,148 @@ cleanup:
}
+static void
+legitimization_check_run (
+ struct TEH_LegitimizationCheckHandle *lch)
+{
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ if (! TEH_enable_kyc)
+ {
+ /* AML/KYC disabled, just immediately return success! */
+ lch->lcr.kyc.requirement_row = 0;
+ lch->lcr.kyc.ok = true;
+ lch->lcr.bad_kyc_auth = false;
+ lch->lcr.expiration_date
+ = GNUNET_TIME_UNIT_FOREVER_TS;
+ memset (&lch->lcr.next_threshold,
+ 0,
+ sizeof (struct TALER_Amount));
+ lch->lcr.http_status = 0;
+ lch->lcr.response = NULL;
+ lch->async_task
+ = GNUNET_SCHEDULER_add_now (
+ &async_return_legi_result,
+ lch);
+ return;
+ }
+ GNUNET_async_scope_enter (&lch->scope,
+ &old_scope);
+ {
+ json_t *jrules;
+ bool no_account_pub;
+ bool no_reserve_pub;
+
+ qs = TEH_plugin->get_kyc_rules (
+ TEH_plugin->cls,
+ &lch->h_payto,
+ &no_account_pub,
+ &lch->lcr.kyc.account_pub,
+ &no_reserve_pub,
+ &lch->lcr.reserve_pub.reserve_pub,
+ &jrules);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_kyc_rules");
+ GNUNET_async_scope_restore (&old_scope);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "get_kyc_rules returned %d/%d/%d/%d\n",
+ (int) qs,
+ ! no_account_pub,
+ ! no_reserve_pub,
+ NULL != jrules);
+
+ lch->lcr.kyc.have_account_pub
+ = ! no_account_pub;
+ lch->lcr.have_reserve_pub
+ = ! no_reserve_pub;
+ if ( (lch->have_merchant_pub) &&
+ ( (! lch->lcr.kyc.have_account_pub) ||
+ (0 !=
+ GNUNET_memcmp (&lch->merchant_pub,
+ &lch->lcr.kyc.account_pub.merchant_pub)) ) &&
+ ( (! lch->lcr.have_reserve_pub) ||
+ (0 !=
+ GNUNET_memcmp (&lch->merchant_pub,
+ &lch->lcr.reserve_pub.merchant_pub)) ) )
+ {
+ if (NULL == jrules)
+ {
+ /* We do not have custom rules, defer enforcing merchant_pub
+ match until we actually have deposit constraints */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC: merchant_pub given but no known target_pub(%d)/reserve_pub(%d) match (%d)!\n",
+ lch->lcr.kyc.have_account_pub,
+ lch->lcr.have_reserve_pub,
+ (int) qs);
+ lch->lcr.bad_kyc_auth = true;
+ }
+ else
+ {
+ /* We have custom rules, but the target_pub for
+ those custom rules does not match the
+ merchant_pub. Fail the KYC process! */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC: merchant_pub does not match target_pub of custom rules!\n");
+ json_decref (jrules);
+ fail_kyc_auth (lch);
+ goto cleanup;
+ }
+ }
+
+ /* parse and free jrules (if we had any) */
+ if (NULL != jrules)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC: have custom KYC rules for this account!\n");
+ lrs = TALER_KYCLOGIC_rules_parse (jrules);
+ GNUNET_break (NULL != lrs);
+ /* Fall back to default rules on parse error! */
+ json_decref (jrules);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC: default KYC rules apply to this account!\n");
+ }
+ }
+
+ if (NULL != lrs)
+ {
+ lch->ru = TALER_EXCHANGEDB_update_rules (TEH_plugin,
+ &TEH_attribute_key,
+ &lch->h_payto,
+ ¤t_rules_cb,
+ lch);
+ }
+ else
+ {
+ struct TALER_EXCHANGEDB_RuleUpdaterResult rur = { 0 };
+
+ current_rules_cb (lch,
+ &rur);
+ }
+ /* FIXME(fdold, 2024-11-08): We are doing the same logic
+ here and in kyc-info, abstract it out? */
+ /* FIXME(cg-2024-12-02): Also some duplication with
+ code around run_measure in taler-exchange-aggregator! */
+cleanup:
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
void
TEH_legitimization_check_cancel (
struct TEH_LegitimizationCheckHandle *lch)
diff --git a/src/exchangedb/exchangedb_aml.c b/src/exchangedb/exchangedb_aml.c
@@ -453,6 +453,13 @@ run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru,
default:
break;
}
+ if (unknown_account)
+ {
+ fail_update (ru,
+ TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
+ NULL);
+ return;
+ }
}
/* The rules remain these rules until the user passes the check */
finish_update (ru);
@@ -485,6 +492,8 @@ update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru)
static void
check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru)
{
+ const struct TALER_KYCLOGIC_Measure *m;
+
ru->depth++;
if (ru->depth > MAX_DEPTH)
{
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
@@ -6258,6 +6258,8 @@ TALER_age_commitment_proof_duplicate (
* @param[in] acp The original age commitment proof
* @param[out] nacp The struct to copy the data into, with freshly allocated and copied keys.
*/
+/* FIXME: API flaw: arguments of this _copy are swapped with
+ the argument order for the other _copy() APIs... */
void
TALER_age_commitment_proof_deep_copy (
const struct TALER_AgeCommitmentProof *acp,
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -1015,7 +1015,8 @@ TALER_KYCLOGIC_rule_get_instant_measure (
const char *measure_name = r->next_measures[i];
const struct TALER_KYCLOGIC_Measure *ms;
- if (0 == strcasecmp (measure_name, KYC_MEASURE_IMPOSSIBLE))
+ if (0 == strcasecmp (measure_name,
+ KYC_MEASURE_IMPOSSIBLE))
{
/* If any of the measures if verboten, we do not even
consider execution of the instant measure. */