gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated: add code to sanity-check KYC con


From: gnunet
Subject: [taler-exchange] branch master updated: add code to sanity-check KYC configuration and KYC decisions
Date: Thu, 16 Feb 2023 16:38:24 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 87a78c6f add code to sanity-check KYC configuration and KYC decisions
87a78c6f is described below

commit 87a78c6f8ce1d50a5f61eb5c3f222cdef0b635ee
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Feb 16 16:37:38 2023 +0100

    add code to sanity-check KYC configuration and KYC decisions
---
 contrib/gana                                     |   2 +-
 src/exchange/taler-exchange-httpd_aml-decision.c | 265 +++++++++++++++++------
 src/include/taler_kyclogic_lib.h                 |  14 ++
 src/kyclogic/kyclogic_api.c                      |  73 ++++++-
 4 files changed, 277 insertions(+), 77 deletions(-)

diff --git a/contrib/gana b/contrib/gana
index 3a616a04..1ec4596b 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit 3a616a04f1cd946bf0641b54cd71f1b858174f74
+Subproject commit 1ec4596bf4925ee24fc06d3e74d2a553b8239870
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c 
b/src/exchange/taler-exchange-httpd_aml-decision.c
index 0f586279..0fd58b9e 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 a629543a..065f2561 100644
--- a/src/include/taler_kyclogic_lib.h
+++ b/src/include/taler_kyclogic_lib.h
@@ -296,6 +296,20 @@ TALER_KYCLOGIC_kyc_get_details (
   void *cb_cls);
 
 
+/**
+ * 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.
  *
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 6d92fce6..b82a6c06 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -21,6 +21,12 @@
 #include "platform.h"
 #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.
  */
@@ -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)
   {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]