aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2024-08-07 19:53:23 +0200
committerChristian Grothoff <christian@grothoff.org>2024-08-07 19:53:23 +0200
commitc08266947f14ee390cbc37a8dd70746215361e8e (patch)
treef8c01835f7380a1c25c0c66f5349f3ab93f94dcb
parentde42b35703ad010040bbbc932ca4aeaa0c582036 (diff)
implement logic to deal with expiration of measures; untested, but could theoretically work
-rw-r--r--src/exchange/taler-exchange-httpd_common_kyc.c295
-rw-r--r--src/exchangedb/Makefile.am1
-rw-r--r--src/exchangedb/exchange_do_insert_aml_decision.sql5
-rw-r--r--src/exchangedb/exchange_do_insert_programmatic_legitimization_outcome.sql189
-rw-r--r--src/exchangedb/pg_insert_programmatic_legitimization_outcome.c92
-rw-r--r--src/exchangedb/pg_insert_programmatic_legitimization_outcome.h56
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c3
-rw-r--r--src/exchangedb/procedures.sql.in1
-rw-r--r--src/include/taler_exchangedb_plugin.h29
-rw-r--r--src/include/taler_kyclogic_lib.h3
-rw-r--r--src/kyclogic/kyclogic_api.c59
11 files changed, 688 insertions, 45 deletions
diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c
index a3239fdea..b01d5fbe6 100644
--- a/src/exchange/taler-exchange-httpd_common_kyc.c
+++ b/src/exchange/taler-exchange-httpd_common_kyc.c
@@ -1013,25 +1013,30 @@ struct TEH_LegitimizationCheckHandle
struct TEH_KycAmlTrigger *kat;
/**
- * Our request scope for logging.
+ * Payto-URI of the account.
*/
- struct GNUNET_AsyncScopeId scope;
+ char *payto_uri;
/**
- * Legitimization result we have been building and
- * should return.
+ * Amount iterator to call to check for amounts.
*/
- struct TEH_LegitimizationCheckResult lcr;
+ TALER_KYCLOGIC_KycAmountIterator ai;
/**
- * Event we were triggered for.
+ * Closure for @e ai.
*/
- enum TALER_KYCLOGIC_KycTriggerEvent et;
+ void *ai_cls;
/**
- * Payto-URI of the account.
+ * Used to keep around the name of the AML program
+ * that we are running via @e aprh.
*/
- char *payto_uri;
+ char *aml_program;
+
+ /**
+ * Handle to AML program we are running, or NULL for none.
+ */
+ struct TALER_KYCLOGIC_AmlProgramRunnerHandle *aprh;
/**
* Hash of @e payto_uri.
@@ -1044,14 +1049,20 @@ struct TEH_LegitimizationCheckHandle
union TALER_AccountPublicKeyP account_pub;
/**
- * Amount iterator to call to check for amounts.
+ * Our request scope for logging.
*/
- TALER_KYCLOGIC_KycAmountIterator ai;
+ struct GNUNET_AsyncScopeId scope;
/**
- * Closure for @e ai.
+ * Legitimization result we have been building and
+ * should return.
*/
- void *ai_cls;
+ struct TEH_LegitimizationCheckResult lcr;
+
+ /**
+ * Event we were triggered for.
+ */
+ enum TALER_KYCLOGIC_KycTriggerEvent et;
/**
* Number of instant rule triggers we have experienced
@@ -1196,6 +1207,225 @@ TEH_legitimization_check (
}
+/**
+ * Type of function called after AML program was run.
+ *
+ * @param cls closure
+ * @param apr result of the AML program.
+ */
+static void
+legi_check_aml_program_cb (
+ void *cls,
+ const struct TALER_KYCLOGIC_AmlProgramResult *apr)
+{
+ struct TEH_LegitimizationCheckHandle *lch = cls;
+
+ lch->aprh = NULL;
+ switch (apr->status)
+ {
+ case TALER_KYCLOGIC_AMLR_SUCCESS:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+ struct GNUNET_TIME_Timestamp expiration_time;
+
+ /* persist outcome of AML program */
+ lrs = TALER_KYCLOGIC_rules_parse (
+ apr->details.success.new_rules);
+ if (NULL == lrs)
+ {
+ GNUNET_break (0);
+ legi_fail (
+ lch,
+ TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE,
+ lch->aml_program);
+ return;
+ }
+ expiration_time
+ = TALER_KYCLOGIC_rules_get_expiration (lrs);
+ TALER_KYCLOGIC_rules_free (lrs);
+ qs = TEH_plugin->insert_programmatic_legitimization_outcome (
+ TEH_plugin->cls,
+ &lch->h_payto,
+ GNUNET_TIME_timestamp_get (),
+ expiration_time.abs_time,
+ apr->details.success.account_properties,
+ apr->details.success.to_investigate,
+ apr->details.success.new_rules,
+ apr->details.success.num_events,
+ apr->details.success.events);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_programmatic_aml_decision");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "insert_programmatic_aml_decision");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ /* Now run with new_rules! */
+ GNUNET_free (lch->aml_program);
+ legitimization_check_run (lch);
+ return;
+ }
+ case TALER_KYCLOGIC_AMLR_FAILURE:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE,
+ lch->aml_program);
+ return;
+ }
+ GNUNET_break (0);
+}
+
+
+/**
+ * We need to run the check from @a kcc for @a lch.
+ *
+ * @param lch legitimization process handle
+ * @param kcc check to trigger
+ */
+static void
+run_check (
+ struct TEH_LegitimizationCheckHandle *lch,
+ const struct TALER_KYCLOGIC_KycCheckContext *kcc)
+{
+ json_t *jmeasures;
+
+ jmeasures
+ = TALER_KYCLOGIC_check_to_measures (kcc);
+ if (NULL == kcc->check)
+ {
+ /* check was skip; directly run AML program */
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *attributes;
+ json_t *aml_history;
+ json_t *kyc_history;
+
+ aml_history = json_array ();
+ kyc_history = json_array ();
+ qs = TEH_plugin->lookup_aml_history (
+ TEH_plugin->cls,
+ &lch->h_payto,
+ &add_aml_history_entry,
+ aml_history);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ json_decref (aml_history);
+ json_decref (kyc_history);
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_aml_history");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* empty history is fine! */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = TEH_plugin->lookup_kyc_history (
+ TEH_plugin->cls,
+ &lch->h_payto,
+ &add_kyc_history_entry,
+ kyc_history);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ json_decref (aml_history);
+ json_decref (kyc_history);
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_kyc_history");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* empty history is fine! */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ attributes
+ = json_object (); /* instant: empty attributes */
+ GNUNET_assert (NULL != attributes);
+ lch->aml_program
+ = GNUNET_strdup (kcc->prog_name);
+ lch->aprh
+ = TALER_KYCLOGIC_run_aml_program (
+ attributes,
+ aml_history,
+ kyc_history,
+ jmeasures,
+ 0, /* measure index */
+ &legi_check_aml_program_cb,
+ lch);
+ json_decref (aml_history);
+ json_decref (kyc_history);
+ json_decref (attributes);
+ if (NULL == lch->aprh)
+ {
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE,
+ NULL);
+ goto cleanup;
+ }
+ }
+ else
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* require kcc.check! */
+ qs = TEH_plugin->trigger_kyc_rule_for_account (
+ TEH_plugin->cls,
+ lch->payto_uri,
+ &lch->h_payto,
+ lch->have_account_pub ? &lch->account_pub : NULL,
+ jmeasures,
+ 0, /* no particular priority */
+ &lch->lcr.kyc.requirement_row);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "trigger_kyc_rule_for_account");
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ legi_fail (lch,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "trigger_kyc_rule_for_account");
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ /* return success! */
+ lch->async_task
+ = GNUNET_SCHEDULER_add_now (
+ &async_return_legi_result,
+ lch);
+ }
+cleanup:
+ json_decref (jmeasures);
+}
+
+
static void
legitimization_check_run (
struct TEH_LegitimizationCheckHandle *lch)
@@ -1230,6 +1460,38 @@ legitimization_check_run (
}
}
+ 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 char *successor;
+ struct TALER_KYCLOGIC_KycCheckContext kcc;
+
+ successor
+ = TALER_KYCLOGIC_rules_get_successor (lrs);
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_requirements_to_check (lrs,
+ NULL,
+ successor,
+ &kcc))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Successor measure `%s' unknown, falling back to default rules!\n",
+ successor);
+ TALER_KYCLOGIC_rules_free (lrs);
+ lrs = NULL;
+ }
+ else
+ {
+ run_check (lch,
+ &kcc);
+ }
+ }
+ }
+
qs = TALER_KYCLOGIC_kyc_test_required (
lch->et,
lrs,
@@ -1260,7 +1522,6 @@ legitimization_check_run (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirement is %s\n",
TALER_KYCLOGIC_rule2s (requirement));
-
instant_ms
= TALER_KYCLOGIC_rule_get_instant_measure (
requirement);
@@ -1357,11 +1618,17 @@ TEH_legitimization_check_cancel (
TEH_kyc_finished_cancel (lch->kat);
lch->kat = NULL;
}
+ if (NULL != lch->aprh)
+ {
+ TALER_KYCLOGIC_run_aml_program_cancel (lch->aprh);
+ lch->aprh = NULL;
+ }
if (NULL != lch->lcr.response)
{
MHD_destroy_response (lch->lcr.response);
lch->lcr.response = NULL;
}
GNUNET_free (lch->payto_uri);
+ GNUNET_free (lch->aml_program);
GNUNET_free (lch);
}
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index 5bfa7a9a9..1407c2f8b 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -127,6 +127,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_insert_drain_profit.h pg_insert_drain_profit.c \
pg_insert_kyc_failure.h pg_insert_kyc_failure.c \
pg_inject_auditor_triggers.h pg_inject_auditor_triggers.c \
+ pg_insert_programmatic_legitimization_outcome.h pg_insert_programmatic_legitimization_outcome.c \
pg_create_tables.h pg_create_tables.c \
pg_event_listen.h pg_event_listen.c \
pg_event_listen_cancel.h pg_event_listen_cancel.c \
diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql
index 360444d26..cf5ff530e 100644
--- a/src/exchangedb/exchange_do_insert_aml_decision.sql
+++ b/src/exchangedb/exchange_do_insert_aml_decision.sql
@@ -188,9 +188,10 @@ INSERT INTO kyc_alerts
(h_payto
,trigger_type)
VALUES
- (in_h_payto,1);
+ (in_h_payto,1)
+ ON CONFLICT DO NOTHING;
- EXECUTE FORMAT (
+EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
diff --git a/src/exchangedb/exchange_do_insert_programmatic_legitimization_outcome.sql b/src/exchangedb/exchange_do_insert_programmatic_legitimization_outcome.sql
new file mode 100644
index 000000000..196632b44
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_programmatic_legitimization_outcome.sql
@@ -0,0 +1,189 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023, 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- 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 <http://www.gnu.org/licenses/>
+--
+
+DROP FUNCTION IF EXISTS exchange_do_insert_programmatic_legitimization_outcome;
+CREATE OR REPLACE FUNCTION exchange_do_insert_programmatic_legitimization_outcome(
+ IN in_h_payto BYTEA,
+ IN in_decision_time INT8,
+ IN in_expiration_time INT8,
+ IN in_properties TEXT,
+ IN in_new_rules TEXT,
+ IN in_to_investigate BOOLEAN,
+ IN ina_events TEXT[],
+ IN in_notify_s TEXT,
+ OUT out_account_unknown BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_outcome_serial_id INT8;
+ my_access_token BYTEA;
+ my_max_dp INT4;
+ my_i INT4;
+ ini_event TEXT;
+BEGIN
+
+out_account_unknown=FALSE;
+
+-- Note: in_payto_uri is allowed to be NULL *if*
+-- in_h_payto is already in wire_targets
+SELECT access_token
+ INTO my_access_token
+ FROM wire_targets
+ WHERE wire_target_h_payto=in_h_payto;
+
+-- Very strange, should never happen that we
+-- take an AML decision on an unknown account!
+IF NOT FOUND
+THEN
+ out_account_unknown=TRUE;
+ RETURN;
+END IF;
+out_account_unknown=FALSE;
+
+-- Did KYC measures get prescribed?
+IF in_jmeasures IS NULL
+THEN
+ -- AML decision without measure: mark all
+ -- active measures finished!
+ UPDATE legitimization_measures
+ SET is_finished=TRUE
+ WHERE access_token=my_access_token
+ AND NOT is_finished;
+ELSE
+ -- Find current maximum DP
+ SELECT COALESCE(MAX(display_priority),0)
+ INTO my_max_dp
+ FROM legitimization_measures
+ WHERE access_token=my_access_token
+ AND NOT is_finished;
+
+ -- First check if a perfectly equivalent legi measure
+ -- already exists, to avoid creating tons of duplicates.
+ UPDATE legitimization_measures
+ SET display_priority=GREATEST(my_max_dp,display_priority)
+ WHERE access_token=my_access_token
+ AND jmeasures=in_jmeasures
+ AND NOT is_finished;
+
+ IF NOT FOUND
+ THEN
+ -- Enable new legitimization measure
+ INSERT INTO legitimization_measures
+ (access_token
+ ,start_time
+ ,jmeasures
+ ,display_priority)
+ VALUES
+ (my_access_token
+ ,in_decision_time
+ ,in_jmeasures
+ ,my_max_dp + 1);
+ END IF;
+
+ -- end if for where we had non-NULL in_jmeasures
+END IF;
+
+UPDATE legitimization_outcomes
+ SET is_active=FALSE
+ WHERE h_payto=in_h_payto
+ -- this clause is a minor optimization to avoid
+ -- updating outcomes that have long expired.
+ AND expiration_time >= in_decision_time;
+
+INSERT INTO legitimization_outcomes
+ (h_payto
+ ,decision_time
+ ,expiration_time
+ ,jproperties
+ ,new_measure_name
+ ,to_investigate
+ ,jnew_rules
+ )
+ VALUES
+ (in_h_payto
+ ,in_decision_time
+ ,in_expiration_time
+ ,in_properties
+ ,in_new_measure_name
+ ,in_to_investigate
+ ,in_new_rules
+ )
+ RETURNING
+ outcome_serial_id
+ INTO
+ my_outcome_serial_id;
+
+-- FIXME: do we want/need programmatic
+-- decisions in the AML history? We
+-- have no justification or decider,
+-- so IF we would need to change the table
+-- significantly, which may break other things...
+
+--INSERT INTO aml_history
+-- (h_payto
+-- ,outcome_serial_id
+-- ,justification
+-- ,decider_pub
+-- ,decider_sig
+-- ) VALUES
+-- (in_h_payto
+-- ,my_outcome_serial_id
+-- ,in_justification
+-- ,in_decider_pub
+-- ,in_decider_sig
+-- );
+
+-- wake up taler-exchange-aggregator
+INSERT INTO kyc_alerts
+ (h_payto
+ ,trigger_type)
+ VALUES
+ (in_h_payto,1)
+ ON CONFLICT DO NOTHING;
+
+IF in_to_investigate
+THEN
+ INSERT INTO aml_status
+ (h_payto
+ ,status)
+ VALUES
+ (in_h_payto
+ ,1)
+ ON CONFLICT (h_payto) DO
+ UPDATE SET status=EXCLUDED.status | 1;
+END IF;
+
+
+FOR i IN 1..COALESCE(array_length(ina_events,1),0)
+LOOP
+ ini_event = ina_events[i];
+ INSERT INTO kyc_events
+ (event_timestamp
+ ,event_type)
+ VALUES
+ (in_collection_time_ts
+ ,ini_event);
+END LOOP;
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_s);
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_programmatic_legitimization_outcome(BYTEA, INT8, INT8, TEXT, TEXT, BOOLEAN, TEXT[], TEXT)
+ IS 'Inserts an AML decision that was taken automatically by an AML program into the database';
diff --git a/src/exchangedb/pg_insert_programmatic_legitimization_outcome.c b/src/exchangedb/pg_insert_programmatic_legitimization_outcome.c
new file mode 100644
index 000000000..1e2b0ac15
--- /dev/null
+++ b/src/exchangedb/pg_insert_programmatic_legitimization_outcome.c
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ 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 <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_programmatic_legitimization_outcome.c
+ * @brief Implementation of the insert_programmatic_legitimization_outcome function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_programmatic_legitimization_outcome.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_programmatic_legitimization_outcome (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp decision_time,
+ struct GNUNET_TIME_Absolute expiration_time,
+ const json_t *account_properties,
+ bool to_investigate,
+ const json_t *new_rules,
+ unsigned int num_events,
+ const char **events)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+
+ char *notify_s
+ = GNUNET_PQ_get_event_notify_channel (&rep.header);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_timestamp (&decision_time),
+ GNUNET_PQ_query_param_absolute_time (&expiration_time),
+ NULL != account_properties
+ ? TALER_PQ_query_param_json (account_properties)
+ : GNUNET_PQ_query_param_null (),
+ TALER_PQ_query_param_json (new_rules),
+ GNUNET_PQ_query_param_bool (to_investigate),
+ GNUNET_PQ_query_param_array_ptrs_string (num_events,
+ events,
+ pg->conn),
+ GNUNET_PQ_query_param_string (notify_s),
+ GNUNET_PQ_query_param_end
+ };
+ bool unknown_account;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_account_unknown",
+ &unknown_account),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "do_insert_programmatic_legitimization_outcome",
+ "SELECT"
+ " out_account_unknown"
+ " FROM exchange_do_insert_programmatic_legitimization_decision"
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "do_insert_programmatic_legitimization_outcome",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ GNUNET_free (notify_s);
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (qs <= 0)
+ return qs;
+ if (unknown_account)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_programmatic_legitimization_outcome.h b/src/exchangedb/pg_insert_programmatic_legitimization_outcome.h
new file mode 100644
index 000000000..82b78c9b5
--- /dev/null
+++ b/src/exchangedb/pg_insert_programmatic_legitimization_outcome.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ 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 <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_programmatic_legitimization_outcome.h
+ * @brief implementation of the insert_programmatic_legitimization_outcome function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PROGRAMMATIC_LEGITIMIZATION_OUTCOME_H
+#define PG_INSERT_PROGRAMMATIC_LEGITIMIZATION_OUTCOME_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Store automated legitimization outcome.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param decision_time when was the decision taken
+ * @param expiration_time when does the data expire
+ * @param account_properties new account properties
+ * @param to_investigate true to flag account for investigation
+ * @param new_rules new KYC rules to apply to the account
+ * @param num_events length of the @a events array
+ * @param events array of KYC events to trigger
+ * @param require_aml true to trigger AML
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_programmatic_legitimization_outcome (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp decision_time,
+ struct GNUNET_TIME_Absolute expiration_time,
+ const json_t *account_properties,
+ bool to_investigate,
+ const json_t *new_rules,
+ unsigned int num_events,
+ const char **events);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 87414125e..dee515a0c 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -47,6 +47,7 @@
#include "pg_get_unfinished_close_requests.h"
#include "pg_insert_close_request.h"
#include "pg_insert_records_by_table.h"
+#include "pg_insert_programmatic_legitimization_outcome.h"
#include "pg_insert_reserve_open_deposit.h"
#include "pg_get_pending_kyc_requirement_process.h"
#include "pg_iterate_kyc_reference.h"
@@ -774,6 +775,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_abort_shard;
plugin->insert_kyc_failure
= &TEH_PG_insert_kyc_failure;
+ plugin->insert_programmatic_legitimization_outcome
+ = &TEH_PG_insert_programmatic_legitimization_outcome;
plugin->complete_shard
= &TEH_PG_complete_shard;
plugin->release_revolving_shard
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
index a92ae77d6..99b932951 100644
--- a/src/exchangedb/procedures.sql.in
+++ b/src/exchangedb/procedures.sql.in
@@ -52,5 +52,6 @@ SET search_path TO exchange;
#include "exchange_do_kycauth_in_insert.sql"
#include "exchange_do_trigger_kyc_rule_for_account.sql"
#include "exchange_do_lookup_kyc_requirement_by_row.sql"
+#include "exchange_do_insert_programmatic_legitimization_outcome.sql"
COMMIT;
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index aa0339d0b..492823ab8 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -7159,6 +7159,34 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * Store automated legitimization outcome.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param decision_time when was the decision taken
+ * @param expiration_time when does the data expire
+ * @param account_properties new account properties
+ * @param to_investigate true to flag account for investigation
+ * @param new_rules new KYC rules to apply to the account
+ * @param num_events length of the @a events array
+ * @param events array of KYC events to trigger
+ * @param require_aml true to trigger AML
+ * @return database transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_programmatic_legitimization_outcome)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp decision_time,
+ struct GNUNET_TIME_Absolute expiration_time,
+ const json_t *account_properties,
+ bool to_investigate,
+ const json_t *new_rules,
+ unsigned int num_events,
+ const char **events);
+
+
+ /**
* Store KYC attribute data, update KYC process status and
* AML status for the given account.
*
@@ -7178,7 +7206,6 @@ struct TALER_EXCHANGEDB_Plugin
* @param events array of KYC events to trigger
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
- * @param require_aml true to trigger AML
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
index f7fb9ed92..9dbcf92fd 100644
--- a/src/include/taler_kyclogic_lib.h
+++ b/src/include/taler_kyclogic_lib.h
@@ -597,7 +597,8 @@ TALER_KYCLOGIC_get_original_measure (
* no trigger has been hit.
*
* @param lrs rule set
- * @param kyc_rule rule that was triggered
+ * @param kyc_rule rule that was triggered, NULL
+ * to merely lookup the measure without any trigger
* @param measure_name selected measure,
* NULL to return the "new_check" set by the @a lrs
* @param[out] kcc set to check to run;
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 56affd010..7c52b93c3 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -857,9 +857,10 @@ TALER_KYCLOGIC_rules_to_limits (const json_t *jrules)
* @param measure_name name of measure to find
* @return NULL if not found, otherwise the measure
*/
-static const struct TALER_KYCLOGIC_Measure *
-find_measure (const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
- const char *measure_name)
+const struct TALER_KYCLOGIC_Measure *
+find_measure (
+ const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
+ const char *measure_name)
{
if (NULL != lrs)
{
@@ -1033,8 +1034,6 @@ TALER_KYCLOGIC_check_to_measures (
json_t *jmeasures;
json_t *mi;
- jmeasures = json_array ();
- GNUNET_assert (NULL != jmeasures);
mi = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("check_name",
check->check_name),
@@ -1043,6 +1042,8 @@ TALER_KYCLOGIC_check_to_measures (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("context",
(json_t *) kcc->context)));
+ jmeasures = json_array ();
+ GNUNET_assert (NULL != jmeasures);
GNUNET_assert (0 ==
json_array_append_new (jmeasures,
mi));
@@ -2500,7 +2501,6 @@ TALER_KYCLOGIC_get_original_measure (
}
-// FIXME: not used in 'main' exchange logic!
enum GNUNET_GenericReturnValue
TALER_KYCLOGIC_requirements_to_check (
const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
@@ -2518,27 +2518,30 @@ TALER_KYCLOGIC_requirements_to_check (
GNUNET_break (0);
return GNUNET_SYSERR;
}
- for (unsigned int i = 0; i<kyc_rule->num_measures; i++)
- {
- if (0 != strcmp (measure_name,
- kyc_rule->next_measures[i]))
- continue;
- found = true;
- break;
- }
- if (! found)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Measure `%s' not allowed for rule `%s'\n",
- measure_name,
- kyc_rule->rule_name);
- return GNUNET_SYSERR;
- }
- if (kyc_rule->verboten)
+ if (NULL != kyc_rule)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Rule says operation is categorically is verboten, cannot take measures\n");
- return GNUNET_SYSERR;
+ for (unsigned int i = 0; i<kyc_rule->num_measures; i++)
+ {
+ if (0 != strcmp (measure_name,
+ kyc_rule->next_measures[i]))
+ continue;
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Measure `%s' not allowed for rule `%s'\n",
+ measure_name,
+ kyc_rule->rule_name);
+ return GNUNET_SYSERR;
+ }
+ if (kyc_rule->verboten)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Rule says operation is categorically is verboten, cannot take measures\n");
+ return GNUNET_SYSERR;
+ }
}
measure = find_measure (lrs,
measure_name);
@@ -2547,7 +2550,9 @@ TALER_KYCLOGIC_requirements_to_check (
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Measure `%s' unknown (but allowed by rule `%s')\n",
measure_name,
- kyc_rule->rule_name);
+ NULL != kyc_rule
+ ? kyc_rule->rule_name
+ : "<NONE>");
return GNUNET_SYSERR;
}