summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2023-02-16 16:37:38 +0100
committerChristian Grothoff <christian@grothoff.org>2023-02-16 16:38:20 +0100
commit87a78c6f8ce1d50a5f61eb5c3f222cdef0b635ee (patch)
treee2221ddcabd2d49bcb7fc9a4f1bbfb094700ced6 /src
parent4d2d0473c3d592d4d6c52ef818a1189afdb5d59d (diff)
downloadexchange-87a78c6f8ce1d50a5f61eb5c3f222cdef0b635ee.tar.gz
exchange-87a78c6f8ce1d50a5f61eb5c3f222cdef0b635ee.tar.bz2
exchange-87a78c6f8ce1d50a5f61eb5c3f222cdef0b635ee.zip
add code to sanity-check KYC configuration and KYC decisions
Diffstat (limited to 'src')
-rw-r--r--src/exchange/taler-exchange-httpd_aml-decision.c265
-rw-r--r--src/include/taler_kyclogic_lib.h14
-rw-r--r--src/kyclogic/kyclogic_api.c73
3 files changed, 276 insertions, 76 deletions
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c
index 0f586279b..0fd58b9ec 100644
--- a/src/exchange/taler-exchange-httpd_aml-decision.c
+++ b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -26,14 +26,138 @@
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
+#include "taler_kyclogic_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd_responses.h"
/**
- * How often do we try the DB operation at most?
+ * Closure for #make_aml_decision()
*/
-#define MAX_RETRIES 10
+struct DecisionContext
+{
+ /**
+ * Justification given for the decision.
+ */
+ const char *justification;
+
+ /**
+ * When was the decision taken.
+ */
+ struct GNUNET_TIME_Timestamp decision_time;
+
+ /**
+ * New threshold for revising the decision.
+ */
+ struct TALER_Amount new_threshold;
+
+ /**
+ * Hash of payto://-URI of affected account.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * New AML state.
+ */
+ enum TALER_AmlDecisionState new_state;
+
+ /**
+ * Signature affirming the decision.
+ */
+ struct TALER_AmlOfficerSignatureP officer_sig;
+
+ /**
+ * Public key of the AML officer.
+ */
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub;
+
+ /**
+ * KYC requirements imposed, NULL for none.
+ */
+ json_t *kyc_requirements;
+
+};
+
+
+/**
+ * Function implementing AML decision database transaction.
+ *
+ * Runs the transaction logic; IF it returns a non-error code, the
+ * transaction logic MUST NOT queue a MHD response. IF it returns an hard
+ * error, the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct DecisionContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+make_aml_decision (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DecisionContext *dc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp last_date;
+ bool invalid_officer;
+
+ qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
+ &dc->h_payto,
+ &dc->new_threshold,
+ dc->new_state,
+ dc->decision_time,
+ dc->justification,
+ dc->kyc_requirements,
+ dc->officer_pub,
+ &dc->officer_sig,
+ &invalid_officer,
+ &last_date);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_aml_decision");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+ }
+ if (invalid_officer)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >=,
+ dc->decision_time))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (NULL != dc->kyc_requirements)
+ {
+ // FIXME: act on these!
+ }
+
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
MHD_RESULT
@@ -43,31 +167,27 @@ TEH_handler_post_aml_decision (
const json_t *root)
{
struct MHD_Connection *connection = rc->connection;
- const char *justification;
- struct GNUNET_TIME_Timestamp decision_time;
- struct TALER_Amount new_threshold;
- struct TALER_PaytoHashP h_payto;
+ struct DecisionContext dc = {
+ .officer_pub = officer_pub
+ };
uint32_t new_state32;
- enum TALER_AmlDecisionState new_state;
- struct TALER_AmlOfficerSignatureP officer_sig;
- json_t *kyc_requirements = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("officer_sig",
- &officer_sig),
+ &dc.officer_sig),
GNUNET_JSON_spec_fixed_auto ("h_payto",
- &h_payto),
+ &dc.h_payto),
TALER_JSON_spec_amount ("new_threshold",
TEH_currency,
- &new_threshold),
+ &dc.new_threshold),
GNUNET_JSON_spec_string ("justification",
- &justification),
+ &dc.justification),
GNUNET_JSON_spec_timestamp ("decision_time",
- &decision_time),
+ &dc.decision_time),
GNUNET_JSON_spec_uint32 ("new_state",
&new_state32),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("kyc_requirements",
- &kyc_requirements),
+ &dc.kyc_requirements),
NULL),
GNUNET_JSON_spec_end ()
};
@@ -86,17 +206,17 @@ TEH_handler_post_aml_decision (
return MHD_YES; /* failure */
}
}
- new_state = (enum TALER_AmlDecisionState) new_state32;
+ dc.new_state = (enum TALER_AmlDecisionState) new_state32;
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
- TALER_officer_aml_decision_verify (justification,
- decision_time,
- &new_threshold,
- &h_payto,
- new_state,
- kyc_requirements,
- officer_pub,
- &officer_sig))
+ TALER_officer_aml_decision_verify (dc.justification,
+ dc.decision_time,
+ &dc.new_threshold,
+ &dc.h_payto,
+ dc.new_state,
+ dc.kyc_requirements,
+ dc.officer_pub,
+ &dc.officer_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
@@ -106,62 +226,67 @@ TEH_handler_post_aml_decision (
NULL);
}
- // FIXME: check kyc_requirements is well-formed!
+ if (NULL != dc.kyc_requirements)
{
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp last_date;
- bool invalid_officer;
- unsigned int retries_left = MAX_RETRIES;
-
- do {
- qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
- &h_payto,
- &new_threshold,
- new_state,
- decision_time,
- justification,
- kyc_requirements,
- officer_pub,
- &officer_sig,
- &invalid_officer,
- &last_date);
- if (0 == --retries_left)
- break;
- } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (qs <= 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "add aml_decision");
- }
- if (NULL != kyc_requirements)
- {
- // FIXME: act on these!
- }
+ size_t index;
+ json_t *elem;
- if (invalid_officer)
+ if (! json_is_array (dc.kyc_requirements))
{
GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
- NULL);
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kyc_requirements must be an array");
}
- if (GNUNET_TIME_timestamp_cmp (last_date,
- >=,
- decision_time))
+
+ json_array_foreach (dc.kyc_requirements, index, elem)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
- NULL);
+ const char *val;
+
+ if (! json_is_string (elem))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kyc_requirements array members must be strings");
+ }
+ val = json_string_value (elem);
+ if (GNUNET_SYSERR ==
+ TALER_KYCLOGIC_check_satisfiable (val))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AML_DECISION_UNKNOWN_CHECK,
+ val);
+ }
+ }
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "make-aml-decision",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &make_aml_decision,
+ &dc))
+ {
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
}
}
+ GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
index a629543a0..065f25618 100644
--- a/src/include/taler_kyclogic_lib.h
+++ b/src/include/taler_kyclogic_lib.h
@@ -297,6 +297,20 @@ TALER_KYCLOGIC_kyc_get_details (
/**
+ * Check if a given @a check_name is a legal name (properly
+ * configured) and can be satisfied in principle.
+ *
+ * @param logic_name name of the logic to match
+ * @return #GNUNET_OK if the check can be satisfied,
+ * #GNUNET_NO if the check can never be satisfied,
+ * #GNUNET_SYSERR if the type of the check is unknown
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_check_satisfiable (
+ const char *check_name);
+
+
+/**
* Obtain the provider logic for a given set of @a requirements.
*
* @param requirements space-separated list of required checks
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 6d92fce68..b82a6c065 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -22,6 +22,12 @@
#include "taler_kyclogic_lib.h"
/**
+ * Name of the KYC check that may never be passed. Useful if some
+ * operations/amounts are categorically forbidden.
+ */
+#define KYC_CHECK_IMPOSSIBLE "impossible"
+
+/**
* Information about a KYC provider.
*/
struct TALER_KYCLOGIC_KycProvider;
@@ -265,6 +271,21 @@ TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
}
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_check_satisfiable (
+ const char *check_name)
+{
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == strcmp (check_name,
+ kyc_checks[i]->name))
+ return GNUNET_OK;
+ if (0 == strcmp (check_name,
+ KYC_CHECK_IMPOSSIBLE))
+ return GNUNET_NO;
+ return GNUNET_SYSERR;
+}
+
+
/**
* Load KYC logic plugin.
*
@@ -331,9 +352,8 @@ add_check (const char *check)
/**
- * Parse list of checks from @a checks and build an
- * array of aliases into the global checks array
- * in @a provided_checks.
+ * Parse list of checks from @a checks and build an array of aliases into the
+ * global checks array in @a provided_checks.
*
* @param[in,out] checks list of checks; clobbered
* @param[out] p_checks where to put array of aliases
@@ -585,6 +605,29 @@ add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_array_append (kyc_triggers,
num_kyc_triggers,
kt);
+ for (unsigned int i = 0; i<kt->num_checks; i++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *ck = kt->required_checks[i];
+
+ if (0 != ck->num_providers)
+ continue;
+ if (0 == strcmp (ck->name,
+ KYC_CHECK_IMPOSSIBLE))
+ continue;
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "Required check `%s' cannot be satisfied: not provided by any provider",
+ ck->name);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "REQUIRED_CHECKS",
+ msg);
+ GNUNET_free (msg);
+ }
+ return GNUNET_SYSERR;
+ }
}
return GNUNET_OK;
}
@@ -614,8 +657,8 @@ struct SectionContext
* @param section name of the section
*/
static void
-handle_section (void *cls,
- const char *section)
+handle_provider_section (void *cls,
+ const char *section)
{
struct SectionContext *sc = cls;
@@ -629,6 +672,21 @@ handle_section (void *cls,
sc->result = false;
return;
}
+}
+
+
+/**
+ * Function to iterate over configuration sections.
+ *
+ * @param cls a `struct SectionContext *`
+ * @param section name of the section
+ */
+static void
+handle_trigger_section (void *cls,
+ const char *section)
+{
+ struct SectionContext *sc = cls;
+
if (0 == strncasecmp (section,
"kyc-legitimization-",
strlen ("kyc-legitimization-")))
@@ -680,7 +738,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
};
GNUNET_CONFIGURATION_iterate_sections (cfg,
- &handle_section,
+ &handle_provider_section,
+ &sc);
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &handle_trigger_section,
&sc);
if (! sc.result)
{