gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: implement #9124 and #9039


From: gnunet
Subject: [taler-exchange] branch master updated: implement #9124 and #9039
Date: Thu, 05 Sep 2024 11:51:53 +0200

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 d96a9b99d implement #9124 and #9039
d96a9b99d is described below

commit d96a9b99d47383308cf8ea8cac5cb9e7705bc7cf
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Sep 5 11:51:41 2024 +0200

    implement #9124 and #9039
---
 contrib/gana                                       |   2 +-
 src/auditor/taler-helper-auditor-coins.c           |   1 -
 src/benchmark/taler-aggregator-benchmark.c         |   4 +-
 src/benchmark/taler-bank-benchmark.c               |   2 +-
 src/exchange/taler-exchange-httpd_aml-decision.c   |  23 +-
 src/exchange/taler-exchange-httpd_kyc-info.c       | 189 +++++++++++----
 src/exchangedb/0005-legitimization_measures.sql    |   4 +-
 src/exchangedb/0005-legitimization_outcomes.sql    |   2 +-
 src/exchangedb/Makefile.am                         |   2 +
 ...nge_do_insert_active_legitimization_measure.sql |  51 +++++
 src/exchangedb/exchange_do_insert_aml_decision.sql |  32 +--
 .../exchange_do_lookup_kyc_requirement_by_row.sql  |  10 +-
 .../pg_insert_active_legitimization_measure.c      |  62 +++++
 .../pg_insert_active_legitimization_measure.h      |  48 ++++
 src/exchangedb/pg_lookup_rules_by_access_token.c   |  70 ++++++
 src/exchangedb/pg_lookup_rules_by_access_token.h   |  44 ++++
 src/exchangedb/plugin_exchangedb_postgres.c        |   6 +
 src/exchangedb/procedures.sql.in                   |   1 +
 src/include/taler_exchange_service.h               |  14 +-
 src/include/taler_exchangedb_plugin.h              |  37 +++
 src/include/taler_kyclogic_lib.h                   |  41 +++-
 src/include/taler_pq_lib.h                         |   2 +-
 src/kyclogic/kyclogic_api.c                        | 253 +++++++++++++++++----
 src/lib/exchange_api_add_aml_decision.c            |  16 +-
 src/testing/test_kyc_api.c                         |   2 +-
 src/testing/testing_api_cmd_take_aml_decision.c    |  12 +-
 26 files changed, 765 insertions(+), 165 deletions(-)

diff --git a/contrib/gana b/contrib/gana
index 7d199a4a0..b4e33545c 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit 7d199a4a0e85592cf0fa4032d9e6a6764e53a593
+Subproject commit b4e33545ccb3a946a215af8a6566d7b9354bd977
diff --git a/src/auditor/taler-helper-auditor-coins.c 
b/src/auditor/taler-helper-auditor-coins.c
index 70605f29c..6cd3aa243 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -2463,7 +2463,6 @@ recoup_refresh_cb (void *cls,
  * @param cls closure, pointer to `enum GNUNET_DB_QueryStatus`
  * @param denom_pub public key, sometimes NULL (!)
  * @param issue issuing information with value, fees and other info about the 
denomination.
- * @return transaction status
  */
 static void
 check_denomination (
diff --git a/src/benchmark/taler-aggregator-benchmark.c 
b/src/benchmark/taler-aggregator-benchmark.c
index 228d050e4..fe484e172 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -119,7 +119,7 @@ eval_probability (float probability)
  * @param x pointer to data to randomize
  */
 #define RANDOMIZE(x) \
-  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof (*x))
+        GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof 
(*x))
 
 
 /**
@@ -342,7 +342,7 @@ add_deposit (const struct Merchant *m)
     }
   }
   if (GNUNET_YES ==
-      eval_probability (((float) refund_rate) / 100.0))
+      eval_probability (((float) refund_rate) / 100.0f))
     return add_refund (m,
                        &d);
   return true;
diff --git a/src/benchmark/taler-bank-benchmark.c 
b/src/benchmark/taler-bank-benchmark.c
index 528798424..340ee3349 100644
--- a/src/benchmark/taler-bank-benchmark.c
+++ b/src/benchmark/taler-bank-benchmark.c
@@ -136,7 +136,7 @@ static struct TALER_TESTING_Timer timings[] = {
  * @param label string to add to the table
  * @return same string, now stored in the table
  */
-const char *
+static const char *
 add_label (char *label)
 {
   if (label_off == label_len)
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c 
b/src/exchange/taler-exchange-httpd_aml-decision.c
index 64d808896..f0049590e 100644
--- a/src/exchange/taler-exchange-httpd_aml-decision.c
+++ b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -40,7 +40,7 @@ TEH_handler_post_aml_decision (
 {
   struct MHD_Connection *connection = rc->connection;
   const char *justification;
-  const char *new_measure = NULL;
+  const char *new_measures = NULL;
   bool to_investigate;
   struct GNUNET_TIME_Timestamp decision_time;
   const json_t *new_rules;
@@ -50,8 +50,8 @@ TEH_handler_post_aml_decision (
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string (
-        "new_measure",
-        &new_measure),
+        "new_measures",
+        &new_measures),
       NULL),
     GNUNET_JSON_spec_string ("justification",
                              &justification),
@@ -96,7 +96,7 @@ TEH_handler_post_aml_decision (
         &h_payto,
         new_rules,
         properties,
-        new_measure,
+        new_measures,
         to_investigate,
         officer_pub,
         &officer_sig))
@@ -123,10 +123,11 @@ TEH_handler_post_aml_decision (
         "legitimization rule malformed");
     }
     expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
-    if (NULL != new_measure)
+    if (NULL != new_measures)
     {
-      jmeasures = TALER_KYCLOGIC_get_measure (lrs,
-                                              new_measure);
+      jmeasures
+        = TALER_KYCLOGIC_get_measures (lrs,
+                                       new_measures);
       if (NULL == jmeasures)
       {
         GNUNET_break_op (0);
@@ -136,7 +137,7 @@ TEH_handler_post_aml_decision (
           connection,
           MHD_HTTP_BAD_REQUEST,
           TALER_EC_GENERIC_PARAMETER_MALFORMED,
-          "new_measure/new_rules");
+          "new_measures/new_rules");
       }
     }
     TALER_KYCLOGIC_rules_free (lrs);
@@ -148,6 +149,8 @@ TEH_handler_post_aml_decision (
     bool invalid_officer = true;
     bool unknown_account = false;
 
+    /* We keep 'new_measures' around mostly so that
+       the auditor can later verify officer_sig */
     qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
                                           &h_payto,
                                           decision_time,
@@ -155,7 +158,7 @@ TEH_handler_post_aml_decision (
                                           properties,
                                           new_rules,
                                           to_investigate,
-                                          new_measure,
+                                          new_measures,
                                           jmeasures,
                                           justification,
                                           officer_pub,
@@ -202,8 +205,6 @@ TEH_handler_post_aml_decision (
         TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
         NULL);
     }
-
-
   }
   return TALER_MHD_reply_static (
     connection,
diff --git a/src/exchange/taler-exchange-httpd_kyc-info.c 
b/src/exchange/taler-exchange-httpd_kyc-info.c
index b2b4a0d8d..1c7b31be0 100644
--- a/src/exchange/taler-exchange-httpd_kyc-info.c
+++ b/src/exchange/taler-exchange-httpd_kyc-info.c
@@ -64,7 +64,13 @@ struct KycPoller
    * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client.  0 for none
    * (or malformed).
    */
-  uint64_t etag_in;
+  uint64_t etag_outcome_in;
+
+  /**
+   * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client.  0 for none
+   * (or malformed).
+   */
+  uint64_t etag_measure_in;
 
   /**
    * When will this request time out?
@@ -77,6 +83,11 @@ struct KycPoller
    */
   struct TALER_AccountAccessTokenP access_token;
 
+  /**
+   * Payto hash of the account matching @a access_token.
+   */
+  struct TALER_PaytoHashP h_payto;
+
   /**
    * True if we are still suspended.
    */
@@ -198,14 +209,18 @@ add_response_headers (void *cls,
  * the LegitimizationMeasures.
  *
  * @param[in,out] kyp request to reply on
- * @param legitimization_measure_row_id etag to set for the response
+ * @param legitimization_measure_row_id part of etag to set for the response
+ * @param legitimization_outcome_row_id part of etag to set for the response
  * @param jmeasures measures to encode
+ * @param jvoluntary array of voluntary measures to encode, can be NULL
  * @return MHD status code
  */
 static MHD_RESULT
 generate_reply (struct KycPoller *kyp,
                 uint64_t legitimization_measure_row_id,
-                const json_t *jmeasures)
+                uint64_t legitimization_outcome_row_id,
+                const json_t *jmeasures,
+                const json_t *jvoluntary)
 {
   const json_t *measures;
   bool is_and_combinator = false;
@@ -289,23 +304,24 @@ generate_reply (struct KycPoller *kyp,
   }
 
   {
-    char etags[64];
+    char etags[128];
     struct MHD_Response *resp;
     MHD_RESULT res;
 
     GNUNET_snprintf (etags,
                      sizeof (etags),
-                     "\"%llu\"",
-                     (unsigned long long) legitimization_measure_row_id);
+                     "\"%llu-%llu\"",
+                     (unsigned long long) legitimization_measure_row_id,
+                     (unsigned long long) legitimization_outcome_row_id);
     resp = TALER_MHD_MAKE_JSON_PACK (
       GNUNET_JSON_pack_array_steal ("requirements",
                                     kris),
       GNUNET_JSON_pack_bool ("is_and_combinator",
                              is_and_combinator),
       GNUNET_JSON_pack_allow_null (
-        /* TODO: support vATTEST-9048 */
-        GNUNET_JSON_pack_object_steal ("voluntary_checks",
-                                       NULL)));
+        GNUNET_JSON_pack_array_incref (
+          "voluntary_measures",
+          (json_t *) jvoluntary)));
     GNUNET_break (MHD_YES ==
                   MHD_add_response_header (resp,
                                            MHD_HTTP_HEADER_ETAG,
@@ -331,7 +347,11 @@ TEH_handler_kyc_info (
   MHD_RESULT res;
   enum GNUNET_DB_QueryStatus qs;
   uint64_t legitimization_measure_last_row;
-  json_t *jmeasures;
+  uint64_t legitimization_outcome_last_row;
+  json_t *jmeasures = NULL;
+  json_t *jvoluntary = NULL;
+  json_t *jnew_rules;
+  struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
 
   if (NULL == kyp)
   {
@@ -368,11 +388,13 @@ TEH_handler_kyc_info (
       if (NULL != etags)
       {
         char dummy;
-        unsigned long long ev;
+        unsigned long long ev1;
+        unsigned long long ev2;
 
-        if (1 != sscanf (etags,
-                         "\"%llu\"%c",
-                         &ev,
+        if (2 != sscanf (etags,
+                         "\"%llu-%llu\"%c",
+                         &ev1,
+                         &ev2,
                          &dummy))
         {
           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -382,24 +404,17 @@ TEH_handler_kyc_info (
         }
         else
         {
-          kyp->etag_in = (uint64_t) ev;
+          kyp->etag_measure_in = (uint64_t) ev1;
+          kyp->etag_outcome_in = (uint64_t) ev2;
         }
       }
     } /* etag */
-  } /* one-time initialization */
-
-  if ( (NULL == kyp->eh) &&
-       GNUNET_TIME_absolute_is_future (kyp->timeout) )
-  {
-    struct TALER_KycCompletedEventP rep = {
-      .header.size = htons (sizeof (rep)),
-      .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED)
-    };
 
+    /* Check access token */
     qs = TEH_plugin->lookup_h_payto_by_access_token (
       TEH_plugin->cls,
       &kyp->access_token,
-      &rep.h_payto);
+      &kyp->h_payto);
     if (qs < 0)
     {
       GNUNET_break (0);
@@ -408,16 +423,63 @@ TEH_handler_kyc_info (
         TALER_EC_GENERIC_DB_FETCH_FAILED,
         "lookup_h_payto_by_access_token");
     }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_REPLY_JSON_PACK (
+        rc->connection,
+        MHD_HTTP_FORBIDDEN,
+        TALER_JSON_pack_ec (
+          TALER_EC_EXCHANGE_KYC_INFO_AUTHORIZATION_FAILED));
 
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Starting DB event listening\n");
-    kyp->eh = TEH_plugin->event_listen (
-      TEH_plugin->cls,
-      GNUNET_TIME_absolute_get_remaining (kyp->timeout),
-      &rep.header,
-      &db_event_cb,
-      rc);
+    }
+
+    if (GNUNET_TIME_absolute_is_future (kyp->timeout))
+    {
+      struct TALER_KycCompletedEventP rep = {
+        .header.size = htons (sizeof (rep)),
+        .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+        .h_payto = kyp->h_payto
+      };
+
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Starting DB event listening\n");
+      kyp->eh = TEH_plugin->event_listen (
+        TEH_plugin->cls,
+        GNUNET_TIME_absolute_get_remaining (kyp->timeout),
+        &rep.header,
+        &db_event_cb,
+        rc);
+    }
+  } /* end of one-time initialization */
+
+  qs = TEH_plugin->lookup_rules_by_access_token (
+    TEH_plugin->cls,
+    &kyp->h_payto,
+    &jnew_rules,
+    &legitimization_outcome_last_row);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_ec (
+      rc->connection,
+      TALER_EC_GENERIC_DB_FETCH_FAILED,
+      "lookup_rules_by_access_token");
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    /* Nothing was triggered, return the measures
+       that apply for any amount. */
+    lrs = NULL;
+  }
+  else
+  {
+    lrs = TALER_KYCLOGIC_rules_parse (jnew_rules);
+    GNUNET_break (NULL != lrs);
+    json_decref (jnew_rules);
   }
+  jvoluntary
+    = TALER_KYCLOGIC_voluntary_measures (lrs);
 
   qs = TEH_plugin->lookup_kyc_status_by_token (
     TEH_plugin->cls,
@@ -427,6 +489,7 @@ TEH_handler_kyc_info (
   if (qs < 0)
   {
     GNUNET_break (0);
+    TALER_KYCLOGIC_rules_free (lrs);
     return TALER_MHD_reply_with_ec (
       rc->connection,
       TALER_EC_GENERIC_DB_FETCH_FAILED,
@@ -434,17 +497,39 @@ TEH_handler_kyc_info (
   }
   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "No KYC requirement open\n");
-    return TALER_MHD_REPLY_JSON_PACK (
-      rc->connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_allow_null (
-        /* TODO: support vATTEST-9048 */
-        GNUNET_JSON_pack_object_steal ("voluntary_checks",
-                                       NULL)));
+    jmeasures
+      = TALER_KYCLOGIC_zero_measures (lrs);
+    if (NULL == jmeasures)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "No KYC requirement open\n");
+      TALER_KYCLOGIC_rules_free (lrs);
+      return TALER_MHD_REPLY_JSON_PACK (
+        rc->connection,
+        MHD_HTTP_OK,
+        GNUNET_JSON_pack_allow_null (
+          GNUNET_JSON_pack_array_steal ("voluntary_measures",
+                                        jvoluntary)));
+    }
+
+    qs = TEH_plugin->insert_active_legitimization_measure (
+      TEH_plugin->cls,
+      &kyp->access_token,
+      jmeasures,
+      &legitimization_measure_last_row);
+    if (qs < 0)
+    {
+      GNUNET_break (0);
+      TALER_KYCLOGIC_rules_free (lrs);
+      return TALER_MHD_reply_with_ec (
+        rc->connection,
+        TALER_EC_GENERIC_DB_STORE_FAILED,
+        "insert_active_legitimization_measure");
+    }
   }
-  if ( (legitimization_measure_last_row == kyp->etag_in) &&
+  TALER_KYCLOGIC_rules_free (lrs);
+  if ( (legitimization_measure_last_row == kyp->etag_measure_in) &&
+       (legitimization_outcome_last_row == kyp->etag_outcome_in) &&
        GNUNET_TIME_absolute_is_future (kyp->timeout) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -461,15 +546,20 @@ TEH_handler_kyc_info (
     MHD_suspend_connection (rc->connection);
     return MHD_YES;
   }
-  if (legitimization_measure_last_row == kyp->etag_in)
+  if ( (legitimization_measure_last_row ==
+        kyp->etag_measure_in) &&
+       (legitimization_outcome_last_row ==
+        kyp->etag_outcome_in) )
   {
-    char etags[64];
+    char etags[128];
 
     json_decref (jmeasures);
+    json_decref (jvoluntary);
     GNUNET_snprintf (etags,
                      sizeof (etags),
-                     "\"%llu\"",
-                     (unsigned long long) legitimization_measure_last_row);
+                     "\"%llu-%llu\"",
+                     (unsigned long long) legitimization_measure_last_row,
+                     (unsigned long long) legitimization_outcome_last_row);
     return TEH_RESPONSE_reply_not_modified (
       rc->connection,
       etags,
@@ -478,8 +568,11 @@ TEH_handler_kyc_info (
   }
   res = generate_reply (kyp,
                         legitimization_measure_last_row,
-                        jmeasures);
+                        legitimization_outcome_last_row,
+                        jmeasures,
+                        jvoluntary);
   json_decref (jmeasures);
+  json_decref (jvoluntary);
   return res;
 }
 
diff --git a/src/exchangedb/0005-legitimization_measures.sql 
b/src/exchangedb/0005-legitimization_measures.sql
index ce4b4e23c..189e71e1f 100644
--- a/src/exchangedb/0005-legitimization_measures.sql
+++ b/src/exchangedb/0005-legitimization_measures.sql
@@ -27,7 +27,7 @@ BEGIN
       ',access_token BYTEA NOT NULL CHECK (LENGTH(access_token)=32)'
       ',start_time INT8 NOT NULL'
       ',jmeasures TEXT NOT NULL'
-      ',display_priority INT4 NOT NULL'
+      ',display_priority INT4 NOT NULL' -- DEAD?
       ',is_finished BOOL NOT NULL DEFAULT(FALSE)'
     ') %s ;'
     ,'legitimization_measures'
@@ -64,7 +64,7 @@ BEGIN
     ,partition_suffix
   );
   PERFORM comment_partitioned_column(
-     'Display priority of the rule that triggered this measure; if in the 
meantime another rule also triggers, the measure is only replaced if the new 
rule has a higher display priority'
+     'Display priority of the rule that triggered this measure; if in the 
meantime another rule also triggers, the measure is only replaced if the new 
rule has a higher display priority; probably not really useful, as right now 
there is only ever one set of legitimization_measures active at any time, might 
be removed in the future'
     ,'display_priority'
     ,'legitimization_measures'
     ,partition_suffix
diff --git a/src/exchangedb/0005-legitimization_outcomes.sql 
b/src/exchangedb/0005-legitimization_outcomes.sql
index 1c7405c99..d132b682f 100644
--- a/src/exchangedb/0005-legitimization_outcomes.sql
+++ b/src/exchangedb/0005-legitimization_outcomes.sql
@@ -61,7 +61,7 @@ BEGIN
     ,partition_suffix
   );
   PERFORM comment_partitioned_column(
-     'name of the measure to trigger immediately, NULL for none'
+     'space-separated list of names of measures to trigger immediately, NULL 
for none, prefixed with a "+" to indicate AND combination for the measures'
     ,'new_measure_name'
     ,'legitimization_outcomes'
     ,partition_suffix
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index 724370efb..5472f41f5 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -149,6 +149,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_iterate_active_auditors.h pg_iterate_active_auditors.c \
   pg_iterate_auditor_denominations.h pg_iterate_auditor_denominations.c \
   pg_reserves_get.h pg_reserves_get.c \
+  pg_lookup_rules_by_access_token.h pg_lookup_rules_by_access_token.c \
   pg_reserves_get_origin.h pg_reserves_get_origin.c \
   pg_drain_kyc_alert.h pg_drain_kyc_alert.c \
   pg_reserves_in_insert.h pg_reserves_in_insert.c \
@@ -211,6 +212,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_wire_prepare_data_mark_failed.h pg_wire_prepare_data_mark_failed.c \
   pg_wire_prepare_data_get.h pg_wire_prepare_data_get.c \
   pg_start_deferred_wire_out.h pg_start_deferred_wire_out.c \
+  pg_insert_active_legitimization_measure.h 
pg_insert_active_legitimization_measure.c \
   pg_store_wire_transfer_out.h pg_store_wire_transfer_out.c \
   pg_gc.h pg_gc.c \
   pg_lookup_kyc_status_by_token.h pg_lookup_kyc_status_by_token.c \
diff --git 
a/src/exchangedb/exchange_do_insert_active_legitimization_measure.sql 
b/src/exchangedb/exchange_do_insert_active_legitimization_measure.sql
new file mode 100644
index 000000000..649ed4685
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_active_legitimization_measure.sql
@@ -0,0 +1,51 @@
+--
+-- 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_active_legitimization_measure;
+CREATE FUNCTION exchange_do_insert_active_legitimization_measure(
+  IN in_access_token BYTEA,
+  IN in_start_time INT8,
+  IN in_jmeasures TEXT,
+  OUT out_legitimization_measure_serial_id INT8)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+UPDATE legitimization_measures
+   SET is_finished=TRUE
+ WHERE access_token=in_access_token
+   AND NOT is_finished;
+
+INSERT INTO legitimization_measures
+  (access_token
+  ,start_time
+  ,jmeasures
+  ,display_priority)
+  VALUES
+  (in_access_token
+  ,in_decision_time
+  ,in_jmeasures
+  ,1)
+  RETURNING
+    legitimization_measure_serial_id
+  INTO
+    out_legitimization_measure_serial_id;
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_active_legitimization_measure(BYTEA, 
INT8, TEXT)
+  IS 'Inserts legitimization measure for an account and marks all existing 
such measures as inactive';
diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql 
b/src/exchangedb/exchange_do_insert_aml_decision.sql
index 4ef8dfdec..4c08d5969 100644
--- a/src/exchangedb/exchange_do_insert_aml_decision.sql
+++ b/src/exchangedb/exchange_do_insert_aml_decision.sql
@@ -36,7 +36,6 @@ AS $$
 DECLARE
   my_outcome_serial_id INT8;
   my_access_token BYTEA;
-  my_max_dp INT4;
 BEGIN
 
 out_account_unknown=FALSE;
@@ -94,28 +93,19 @@ THEN
   RETURN;
 END IF;
 
+-- AML decision: mark all active measures finished!
+UPDATE legitimization_measures
+   SET is_finished=TRUE
+ WHERE access_token=my_access_token
+   AND NOT is_finished;
+
 -- Did KYC measures get prescribed?
-IF in_jmeasures IS NULL
+IF in_jmeasures IS NOT 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)
+  PERFORM
+    FROM legitimization_measures
    WHERE access_token=my_access_token
      AND jmeasures=in_jmeasures
      AND NOT is_finished;
@@ -132,10 +122,10 @@ ELSE
       (my_access_token
       ,in_decision_time
       ,in_jmeasures
-      ,my_max_dp + 1);
+      ,1);
   END IF;
 
-  -- end if for where we had non-NULL in_jmeasures
+  -- end if for where we had in_jmeasures
 END IF;
 
 UPDATE legitimization_outcomes
diff --git a/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql 
b/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql
index cfd4d42d9..e9a22aa84 100644
--- a/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql
+++ b/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql
@@ -52,17 +52,13 @@ out_account_pub = my_wtrec.target_pub;
 out_access_token = my_wtrec.access_token;
 
 -- Check if there are active measures for the account.
-SELECT NOT is_finished
-  INTO out_kyc_required
+PERFORM
   FROM legitimization_measures
  WHERE access_token=out_access_token
- ORDER BY start_time DESC
+   AND NOT is_finished
  LIMIT 1;
 
-IF NOT FOUND
-THEN
-  out_kyc_required=TRUE;
-END IF;
+out_kyc_required = FOUND;
 
 -- Get currently applicable rules.
 -- Only one should ever be active per account.
diff --git a/src/exchangedb/pg_insert_active_legitimization_measure.c 
b/src/exchangedb/pg_insert_active_legitimization_measure.c
new file mode 100644
index 000000000..0e6757b76
--- /dev/null
+++ b/src/exchangedb/pg_insert_active_legitimization_measure.c
@@ -0,0 +1,62 @@
+/*
+   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_active_legitimization_measure.c
+ * @brief Implementation of the insert_active_legitimization_measure 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_active_legitimization_measure.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_active_legitimization_measure (
+  void *cls,
+  const struct TALER_AccountAccessTokenP *access_token,
+  const json_t *jmeasures,
+  uint64_t *legitimization_measure_serial_id)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Timestamp now
+    = GNUNET_TIME_timestamp_get ();
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (access_token),
+    GNUNET_PQ_query_param_timestamp (&now),
+    TALER_PQ_query_param_json (jmeasures),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_uint64 ("out_legitimization_measure_serial_id",
+                                  legitimization_measure_serial_id),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "do_insert_active_legitimization_measure",
+           "SELECT"
+           " out_legitimization_measure_serial_id"
+           " FROM exchange_do_insert_active_legitimization_measure"
+           "($1, $2);");
+  return GNUNET_PQ_eval_prepared_singleton_select (
+    pg->conn,
+    "do_insert_active_legitimization_measure",
+    params,
+    rs);
+}
diff --git a/src/exchangedb/pg_insert_active_legitimization_measure.h 
b/src/exchangedb/pg_insert_active_legitimization_measure.h
new file mode 100644
index 000000000..09b558d83
--- /dev/null
+++ b/src/exchangedb/pg_insert_active_legitimization_measure.h
@@ -0,0 +1,48 @@
+/*
+   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_active_legitimization_measure.h
+ * @brief implementation of the insert_active_legitimization_measure function 
for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_ACTIVE_LEGITIMIZATION_MEASURE_H
+#define PG_INSERT_ACTIVE_LEGITIMIZATION_MEASURE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Create new active legitimization measure.
+ *
+ *
+ * @param cls closure
+ * @param access_token access token that identifies the
+ *   account the legitimization measures apply to
+ * @param jmeasures new legitimization measures
+ * @param[out] legitimization_measure_serial_id
+ *    set to new row in legitimization_measures table
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_active_legitimization_measure (
+  void *cls,
+  const struct TALER_AccountAccessTokenP *access_token,
+  const json_t *jmeasures,
+  uint64_t *legitimization_measure_serial_id);
+
+
+#endif
diff --git a/src/exchangedb/pg_lookup_rules_by_access_token.c 
b/src/exchangedb/pg_lookup_rules_by_access_token.c
new file mode 100644
index 000000000..0bfdeb5d2
--- /dev/null
+++ b/src/exchangedb/pg_lookup_rules_by_access_token.c
@@ -0,0 +1,70 @@
+/*
+   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_lookup_rules_by_access_token.c
+ * @brief Implementation of the lookup_rules_by_access_token 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_lookup_rules_by_access_token.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_rules_by_access_token (
+  void *cls,
+  const struct TALER_PaytoHashP *h_payto,
+  json_t **jnew_rules,
+  uint64_t *rowid)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (h_payto),
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    TALER_PQ_result_spec_json (
+      "jnew_rules",
+      jnew_rules),
+    GNUNET_PQ_result_spec_uint64 (
+      "row_id",
+      rowid),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "lookup_rules_by_access_token",
+           "SELECT"
+           " jnew_rules"
+           ",outcome_serial_id AS row_id"
+           " FROM legitimization_outcomes"
+           " WHERE h_payto=$1"
+           "   AND expiration_time>$2"
+           "   AND is_active"
+           " ORDER BY expiration_time DESC"
+           " LIMIT 1;");
+  now = GNUNET_TIME_absolute_get ();
+  return GNUNET_PQ_eval_prepared_singleton_select (
+    pg->conn,
+    "lookup_rules_by_access_token",
+    params,
+    rs);
+}
diff --git a/src/exchangedb/pg_lookup_rules_by_access_token.h 
b/src/exchangedb/pg_lookup_rules_by_access_token.h
new file mode 100644
index 000000000..e1824382c
--- /dev/null
+++ b/src/exchangedb/pg_lookup_rules_by_access_token.h
@@ -0,0 +1,44 @@
+/*
+   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_lookup_rules_by_access_token.h
+ * @brief implementation of the lookup_rules_by_access_token function for 
Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_RULES_BY_ACCESS_TOKEN_H
+#define PG_LOOKUP_RULES_BY_ACCESS_TOKEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup KYC rules by account access token.
+ *
+ * @param cls closure
+ * @param h_payto account hash to look under
+ * @param[out] jnew_rules set to active LegitimizationRuleSet
+ * @param[out] rowid set to outcome_serial_id of the row
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_rules_by_access_token (
+  void *cls,
+  const struct TALER_PaytoHashP *h_payto,
+  json_t **jnew_rules,
+  uint64_t *rowid);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 57eb1b028..b5d410fc1 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -42,6 +42,7 @@
 #include "pg_do_reserve_open.h"
 #include "pg_get_coin_transactions.h"
 #include "pg_get_expired_reserves.h"
+#include "pg_lookup_rules_by_access_token.h"
 #include "pg_lookup_h_payto_by_access_token.h"
 #include "pg_get_purse_request.h"
 #include "pg_get_reserve_history.h"
@@ -86,6 +87,7 @@
 #include "pg_update_kyc_process_by_row.h"
 #include "pg_insert_kyc_requirement_process.h"
 #include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_insert_active_legitimization_measure.h"
 #include "pg_select_merge_amounts_for_kyc_check.h"
 #include "pg_profit_drains_set_finished.h"
 #include "pg_profit_drains_get_pending.h"
@@ -585,6 +587,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_iterate_active_auditors;
   plugin->iterate_auditor_denominations
     = &TEH_PG_iterate_auditor_denominations;
+  plugin->lookup_rules_by_access_token
+    = &TEH_PG_lookup_rules_by_access_token;
   plugin->reserves_get
     = &TEH_PG_reserves_get;
   plugin->reserves_get_origin
@@ -813,6 +817,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_test_aml_officer;
   plugin->lookup_aml_officer
     = &TEH_PG_lookup_aml_officer;
+  plugin->insert_active_legitimization_measure
+    = &TEH_PG_insert_active_legitimization_measure;
   plugin->trigger_aml_process
     = &TEH_PG_trigger_aml_process;
   plugin->insert_aml_decision
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
index b7c29ae7f..0c25e8f5d 100644
--- a/src/exchangedb/procedures.sql.in
+++ b/src/exchangedb/procedures.sql.in
@@ -54,6 +54,7 @@ SET search_path TO exchange;
 #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"
+#include "exchange_do_insert_active_legitimization_measure.sql"
 #include "exchange_do_select_aggregations_above_serial.sql"
 
 COMMIT;
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index f49c0826a..f6907138f 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -6395,8 +6395,10 @@ struct TALER_EXCHANGE_AccountRule
  *                      decision is about
  * @param decision_time when was the decision made
  * @param successor_measure measure to activate after @a expiration_time if no 
rule applied
- * @param new_check new KYC check to provide to the user,
- *          NULL for none
+ * @param new_measures space-separated list of measures
+ *   to trigger immediately;
+ "   "+" prefixed for AND combination;
+ *   NULL for none
  * @param expiration_time when do the new rules expire
  * @param num_rules length of the @a rules array
  * @param rules new rules for the account
@@ -6411,13 +6413,13 @@ struct TALER_EXCHANGE_AccountRule
  * @return the request handle; NULL upon error
  */
 struct TALER_EXCHANGE_AddAmlDecision *
-TALER_EXCHANGE_add_aml_decision (
+TALER_EXCHANGE_post_aml_decision (
   struct GNUNET_CURL_Context *ctx,
   const char *url,
   const struct TALER_PaytoHashP *h_payto,
   struct GNUNET_TIME_Timestamp decision_time,
   const char *successor_measure,
-  const char *new_check,
+  const char *new_measures,
   struct GNUNET_TIME_Timestamp expiration_time,
   unsigned int num_rules,
   const struct TALER_EXCHANGE_AccountRule *rules,
@@ -6432,12 +6434,12 @@ TALER_EXCHANGE_add_aml_decision (
 
 
 /**
- * Cancel #TALER_EXCHANGE_add_aml_decision() operation.
+ * Cancel #TALER_EXCHANGE_post_aml_decision() operation.
  *
  * @param rh handle of the operation to cancel
  */
 void
-TALER_EXCHANGE_add_aml_decision_cancel (
+TALER_EXCHANGE_post_aml_decision_cancel (
   struct TALER_EXCHANGE_AddAmlDecision *rh);
 
 
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 48671fc51..44e1b8cfb 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -7063,6 +7063,23 @@ struct TALER_EXCHANGEDB_Plugin
     json_t **jmeasures);
 
 
+  /**
+   * Lookup KYC rules by account access token.
+   *
+   * @param cls closure
+   * @param h_payto account payto hash to look under
+   * @param[out] jnew_rules set to active LegitimizationRuleSet
+   * @param[out] rowid row of the last legitimization outcome
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+    (*lookup_rules_by_access_token)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    json_t **jnew_rules,
+    uint64_t *rowid);
+
+
   /**
    * Lookup KYC process meta data.
    *
@@ -7611,6 +7628,26 @@ struct TALER_EXCHANGEDB_Plugin
     json_t **jmeasures);
 
 
+  /**
+   * Create new active legitimization measure.
+   *
+   *
+   * @param cls closure
+   * @param access_token access token that identifies the
+   *   account the legitimization measures apply to
+   * @param jmeasures new legitimization measures
+   * @param[out] legitimization_measure_serial_id
+   *    set to new row in legitimization_measures table
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+    (*insert_active_legitimization_measure) (
+    void *cls,
+    const struct TALER_AccountAccessTokenP *access_token,
+    const json_t *jmeasures,
+    uint64_t *legitimization_measure_serial_id);
+
+
   /**
    * Insert an AML decision. Inserts into AML history and insert or updates AML
    * status.
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
index 2387ac074..fdfd5e283 100644
--- a/src/include/taler_kyclogic_lib.h
+++ b/src/include/taler_kyclogic_lib.h
@@ -163,11 +163,6 @@ struct TALER_KYCLOGIC_KycCheck
    */
   unsigned int num_outputs;
 
-  /**
-   * True if clients can voluntarily trigger this check.
-   */
-  bool voluntary;
-
   /**
    * Type of the KYC check.
    */
@@ -382,6 +377,32 @@ json_t *
 TALER_KYCLOGIC_get_zero_limits (void);
 
 
+/**
+ * Obtain set of all measures that
+ * could be triggered at an amount of zero and that
+ * thus might be requested before a client even
+ * has performed any operation.
+ *
+ * @param lrs rule set to investigate, NULL for default
+ * @return LegitimizationMeasures, NULL on error
+ */
+json_t *
+TALER_KYCLOGIC_zero_measures (
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs);
+
+
+/**
+ * Obtain set of all voluntary measures that
+ * could be triggered by clients at will.
+ *
+ * @param lrs rule set to investigate, NULL for default
+ * @return array of MeasureInformation, never NULL
+ */
+json_t *
+TALER_KYCLOGIC_voluntary_measures (
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs);
+
+
 /**
  * Get human-readable name of KYC rule.
  *
@@ -564,17 +585,17 @@ TALER_KYCLOGIC_measure_to_requirement (
 
 
 /**
- * Lookup @a measure_name in @a lrs and create JSON
- * object with the corresponding LegitimizationMeasures.
+ * Lookup measures from @a measures_spec in @a lrs and create JSON object with
+ * the corresponding LegitimizationMeasures.
  *
  * @param lrs set of legitimization rules
- * @param measure_name name of a measure to trigger from @a lrs
+ * @param measures_spec space-separated set of a measures to trigger from @a 
lrs; "+"-prefixed if AND-cominbation applies
  * @return JSON object of type LegitimizationMeasures
  */
 json_t *
-TALER_KYCLOGIC_get_measure (
+TALER_KYCLOGIC_get_measures (
   const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
-  const char *measure_name);
+  const char *measures_spec);
 
 /**
  * Lookup the provider for the given @a check_name.
diff --git a/src/include/taler_pq_lib.h b/src/include/taler_pq_lib.h
index 860be0e4d..e9929a0e9 100644
--- a/src/include/taler_pq_lib.h
+++ b/src/include/taler_pq_lib.h
@@ -188,7 +188,7 @@ TALER_PQ_query_param_array_hash_code (
  * `struct TALER_DenominationHashP`
  *
  * @param num number of elements in @e hash_codes
- * @param hashes array of GNUNET_HashCode
+ * @param denom_hs array of denomination hashes to encode
  * @param db context for the db-connection
  */
 struct GNUNET_PQ_QueryParam
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 16cd16c72..4aaf5d014 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -155,6 +155,10 @@ struct TALER_KYCLOGIC_Measure
    */
   json_t *context;
 
+  /**
+   * Can this measure be triggered voluntarily?
+   */
+  bool voluntary;
 };
 
 
@@ -628,7 +632,8 @@ TALER_KYCLOGIC_rules_parse (const json_t *jlrs)
             GNUNET_break (0);
             goto cleanup;
           }
-          rule->next_measures[j] = GNUNET_strdup (str);
+          rule->next_measures[j]
+            = GNUNET_strdup (str);
         }
       }
     }
@@ -650,6 +655,7 @@ TALER_KYCLOGIC_rules_parse (const json_t *jlrs)
       const char *check_name;
       const char *prog_name;
       const json_t *context = NULL;
+      bool voluntary = false;
       struct TALER_KYCLOGIC_Measure *measure
         = &lrs->custom_measures[off++];
       struct GNUNET_JSON_Specification ispec[] = {
@@ -661,6 +667,10 @@ TALER_KYCLOGIC_rules_parse (const json_t *jlrs)
           GNUNET_JSON_spec_array_const ("context",
                                         &context),
           NULL),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_bool ("voluntary",
+                                 &voluntary),
+          NULL),
         GNUNET_JSON_spec_end ()
       };
 
@@ -678,6 +688,8 @@ TALER_KYCLOGIC_rules_parse (const json_t *jlrs)
         = GNUNET_strdup (check_name);
       measure->prog_name
         = GNUNET_strdup (prog_name);
+      measure->voluntary
+        = voluntary;
       if (NULL != context)
         measure->context
           = json_incref ((json_t*) context);
@@ -980,35 +992,105 @@ TALER_KYCLOGIC_rule_to_measures (
 
 
 json_t *
-TALER_KYCLOGIC_get_measure (
-  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
-  const char *measure_name)
+TALER_KYCLOGIC_zero_measures (
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs)
 {
-  const struct TALER_KYCLOGIC_Measure *ms;
-  json_t *mi;
-  json_t *jmeasures;
+  json_t *zero_measures;
+  const struct TALER_KYCLOGIC_KycRule *rules;
+  unsigned int num_rules;
 
-  if (0 == strcasecmp ("verboten",
-                       measure_name))
-  {
-    jmeasures = json_array ();
-    GNUNET_assert (NULL != jmeasures);
-    return GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_array_steal ("measures",
-                                    jmeasures),
-      GNUNET_JSON_pack_bool ("is_and_combinator",
-                             false),
-      GNUNET_JSON_pack_bool ("verboten",
-                             true));
-  }
-  ms = find_measure (lrs,
-                     measure_name);
-  if (NULL == ms)
+  if (NULL == lrs)
+    lrs = &default_rules;
+  rules = lrs->kyc_rules;
+  num_rules = lrs->num_kyc_rules;
+  zero_measures = json_array ();
+  GNUNET_assert (NULL != zero_measures);
+  for (unsigned int i = 0; i<num_rules; i++)
   {
-    GNUNET_break (0);
-    return NULL;
+    const struct TALER_KYCLOGIC_KycRule *rule = &rules[i];
+
+    if (! rule->exposed)
+      continue;
+    if (rule->verboten)
+      continue; /* see: hard_limits */
+    if (! TALER_amount_is_zero (&rule->threshold))
+      continue;
+    for (unsigned int j = 0; j<rule->num_measures; j++)
+    {
+      const struct TALER_KYCLOGIC_Measure *ms;
+      json_t *mi;
+
+      ms = find_measure (lrs,
+                         rule->next_measures[j]);
+      if (NULL == ms)
+      {
+        GNUNET_break (0);
+        json_decref (zero_measures);
+        return NULL;
+      }
+      if (0 == strcasecmp ("verboten",
+                           ms->check_name))
+        continue; /* not a measure to be selected */
+      mi = GNUNET_JSON_PACK (
+        TALER_JSON_pack_kycte ("operation_type",
+                               rule->trigger),
+        GNUNET_JSON_pack_string ("check_name",
+                                 ms->check_name),
+        GNUNET_JSON_pack_string ("prog_name",
+                                 ms->prog_name),
+        GNUNET_JSON_pack_allow_null (
+          GNUNET_JSON_pack_object_incref ("context",
+                                          ms->context)));
+      GNUNET_assert (0 ==
+                     json_array_append_new (zero_measures,
+                                            mi));
+    }
   }
-  mi = GNUNET_JSON_PACK (
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_array_steal ("measures",
+                                  zero_measures),
+    /* Zero-measures are always OR */
+    GNUNET_JSON_pack_bool ("is_and_combinator",
+                           false),
+    /* OR means verboten measures do not matter */
+    GNUNET_JSON_pack_bool ("verboten",
+                           false));
+}
+
+
+/**
+ * Check if @a ms is a voluntary measure, and if so
+ * convert to JSON and append to @a voluntary_measures.
+ *
+ * @param[in,out] voluntary_measures JSON array of MeasureInformation
+ * @param ms a measure to possibly append
+ */
+static void
+append_voluntary_measure (
+  json_t *voluntary_measures,
+  const struct TALER_KYCLOGIC_Measure *ms)
+{
+#if 0
+  json_t *mj;
+#endif
+
+  if (! ms->voluntary)
+    return;
+  if (0 == strcasecmp ("verboten",
+                       ms->check_name))
+    return; /* very strange configuration */
+#if 0
+  /* TODO: support vATTEST-9048 (this API in kyclogic!) */
+  // NOTE: need to convert ms to "KycRequirementInformation"
+  // *and* in particular generate "id" values that
+  // are then understood to refer to the voluntary measures
+  // by the rest of the API (which is the hard part!)
+  // => need to change the API to encode the
+  // legitimization_outcomes row ID of the lrs from
+  // which the voluntary 'ms' originated, and
+  // then update the kyc-upload/kyc-start endpoints
+  // to recognize the new ID format!
+  mj = GNUNET_JSON_PACK (
     GNUNET_JSON_pack_string ("check_name",
                              ms->check_name),
     GNUNET_JSON_pack_string ("prog_name",
@@ -1016,18 +1098,113 @@ TALER_KYCLOGIC_get_measure (
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("context",
                                       ms->context)));
+  GNUNET_assert (0 ==
+                 json_array_append_new (voluntary_measures,
+                                        mj));
+#endif
+}
+
+
+json_t *
+TALER_KYCLOGIC_voluntary_measures (
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs)
+{
+  json_t *voluntary_measures;
+
+  voluntary_measures = json_array ();
+  GNUNET_assert (NULL != voluntary_measures);
+  if (NULL != lrs)
+  {
+    for (unsigned int i = 0; i<lrs->num_custom_measures; i++)
+    {
+      const struct TALER_KYCLOGIC_Measure *ms
+        = &lrs->custom_measures[i];
+
+      append_voluntary_measure (voluntary_measures,
+                                ms);
+    }
+  }
+  for (unsigned int i = 0; i<default_rules.num_custom_measures; i++)
+  {
+    const struct TALER_KYCLOGIC_Measure *ms
+      = &default_rules.custom_measures[i];
+
+    append_voluntary_measure (voluntary_measures,
+                              ms);
+  }
+  return voluntary_measures;
+}
+
+
+json_t *
+TALER_KYCLOGIC_get_measures (
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
+  const char *measures_spec)
+{
+  json_t *jmeasures;
+  char *nm;
+  bool verboten = false;
+  bool is_and = false;
+
+  if ('+' == measures_spec[0])
+  {
+    nm = GNUNET_strdup (&measures_spec[1]);
+    is_and = true;
+  }
+  else
+  {
+    nm = GNUNET_strdup (measures_spec);
+  }
   jmeasures = json_array ();
   GNUNET_assert (NULL != jmeasures);
-  GNUNET_assert (0 ==
-                 json_array_append_new (jmeasures,
-                                        mi));
+  for (const char *tok = strtok (nm, " ");
+       NULL != tok;
+       tok = strtok (NULL, " "))
+  {
+    const struct TALER_KYCLOGIC_Measure *ms;
+    json_t *mi;
+
+    if (0 == strcasecmp ("verboten",
+                         tok))
+    {
+      verboten = true;
+      continue;
+    }
+    ms = find_measure (lrs,
+                       tok);
+    if (NULL == ms)
+    {
+      GNUNET_break (0);
+      GNUNET_free (nm);
+      json_decref (jmeasures);
+      return NULL;
+    }
+    if (0 == strcasecmp ("verboten",
+                         ms->check_name))
+    {
+      verboten = true;
+      continue;
+    }
+    mi = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("check_name",
+                               ms->check_name),
+      GNUNET_JSON_pack_string ("prog_name",
+                               ms->prog_name),
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_object_incref ("context",
+                                        ms->context)));
+    GNUNET_assert (0 ==
+                   json_array_append_new (jmeasures,
+                                          mi));
+  }
+  GNUNET_free (nm);
   return GNUNET_JSON_PACK (
     GNUNET_JSON_pack_array_steal ("measures",
                                   jmeasures),
     GNUNET_JSON_pack_bool ("is_and_combinator",
-                           false),
+                           is_and),
     GNUNET_JSON_pack_bool ("verboten",
-                           false));
+                           verboten));
 }
 
 
@@ -1487,7 +1664,6 @@ static enum GNUNET_GenericReturnValue
 add_check (const struct GNUNET_CONFIGURATION_Handle *cfg,
            const char *section)
 {
-  bool voluntary;
   enum TALER_KYCLOGIC_CheckType ct;
   char *description = NULL;
   json_t *description_i18n = NULL;
@@ -1505,11 +1681,6 @@ add_check (const struct GNUNET_CONFIGURATION_Handle *cfg,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Parsing KYC check %s\n",
               section);
-  voluntary = (GNUNET_YES ==
-               GNUNET_CONFIGURATION_get_value_yesno (cfg,
-                                                     section,
-                                                     "VOLUNTARY"));
-
   {
     char *type_s;
 
@@ -1683,7 +1854,6 @@ add_check (const struct GNUNET_CONFIGURATION_Handle *cfg,
       break;
     }
     kc->check_name = GNUNET_strdup (&section[strlen ("kyc-check-")]);
-    kc->voluntary = voluntary;
     kc->description = description;
     kc->description_i18n = description_i18n;
     kc->fallback = fallback;
@@ -2066,6 +2236,7 @@ static enum GNUNET_GenericReturnValue
 add_measure (const struct GNUNET_CONFIGURATION_Handle *cfg,
              const char *section)
 {
+  bool voluntary;
   char *check_name = NULL;
   char *context_str = NULL;
   char *program = NULL;
@@ -2094,6 +2265,11 @@ add_measure (const struct GNUNET_CONFIGURATION_Handle 
*cfg,
                                "PROGRAM");
     goto fail;
   }
+  voluntary = (GNUNET_YES ==
+               GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                                     section,
+                                                     "VOLUNTARY"));
+
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              section,
@@ -2125,6 +2301,7 @@ add_measure (const struct GNUNET_CONFIGURATION_Handle 
*cfg,
     m.check_name = check_name;
     m.prog_name = program;
     m.context = context;
+    m.voluntary = voluntary;
     GNUNET_array_append (default_rules.custom_measures,
                          default_rules.num_custom_measures,
                          m);
diff --git a/src/lib/exchange_api_add_aml_decision.c 
b/src/lib/exchange_api_add_aml_decision.c
index a9a41a0aa..abd348c4a 100644
--- a/src/lib/exchange_api_add_aml_decision.c
+++ b/src/lib/exchange_api_add_aml_decision.c
@@ -119,18 +119,18 @@ handle_add_aml_decision_finished (void *cls,
             &adr);
     wh->cb = NULL;
   }
-  TALER_EXCHANGE_add_aml_decision_cancel (wh);
+  TALER_EXCHANGE_post_aml_decision_cancel (wh);
 }
 
 
 struct TALER_EXCHANGE_AddAmlDecision *
-TALER_EXCHANGE_add_aml_decision (
+TALER_EXCHANGE_post_aml_decision (
   struct GNUNET_CURL_Context *ctx,
   const char *url,
   const struct TALER_PaytoHashP *h_payto,
   struct GNUNET_TIME_Timestamp decision_time,
   const char *successor_measure,
-  const char *new_measure,
+  const char *new_measures,
   struct GNUNET_TIME_Timestamp expiration_time,
   unsigned int num_rules,
   const struct TALER_EXCHANGE_AccountRule *rules,
@@ -229,7 +229,7 @@ TALER_EXCHANGE_add_aml_decision (
                                    h_payto,
                                    new_rules,
                                    properties,
-                                   new_measure,
+                                   new_measures,
                                    keep_investigating,
                                    officer_priv,
                                    &officer_sig);
@@ -274,8 +274,8 @@ TALER_EXCHANGE_add_aml_decision (
     GNUNET_JSON_pack_object_incref ("properties",
                                     (json_t *) properties),
     GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("new_measure",
-                               new_measure)),
+      GNUNET_JSON_pack_string ("new_measures",
+                               new_measures)),
     GNUNET_JSON_pack_bool ("keep_investigating",
                            keep_investigating),
     GNUNET_JSON_pack_data_auto ("officer_sig",
@@ -307,7 +307,7 @@ TALER_EXCHANGE_add_aml_decision (
                                   wh);
   if (NULL == wh->job)
   {
-    TALER_EXCHANGE_add_aml_decision_cancel (wh);
+    TALER_EXCHANGE_post_aml_decision_cancel (wh);
     return NULL;
   }
   return wh;
@@ -315,7 +315,7 @@ TALER_EXCHANGE_add_aml_decision (
 
 
 void
-TALER_EXCHANGE_add_aml_decision_cancel (
+TALER_EXCHANGE_post_aml_decision_cancel (
   struct TALER_EXCHANGE_AddAmlDecision *wh)
 {
   if (NULL != wh->job)
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index 3ee68f30e..3af972569 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -704,7 +704,7 @@ run (void *cls,
       "     ,\"verboten\":false"
       "   }"
       " ]" /* end new rules */
-      ",\"new_measure\":\"form-measure\""
+      ",\"new_measures\":\"form-measure\""
       ",\"custom_measures\":"
       "  {"
       "    \"form-measure\":"
diff --git a/src/testing/testing_api_cmd_take_aml_decision.c 
b/src/testing/testing_api_cmd_take_aml_decision.c
index b3976b82e..ce2cd8e92 100644
--- a/src/testing/testing_api_cmd_take_aml_decision.c
+++ b/src/testing/testing_api_cmd_take_aml_decision.c
@@ -149,7 +149,7 @@ take_aml_decision_run (void *cls,
   const json_t *jmeasures = NULL;
   struct GNUNET_TIME_Timestamp expiration_time
     = GNUNET_TIME_relative_to_timestamp (ds->expiration_delay);
-  const char *new_measure = NULL;
+  const char *new_measures = NULL;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_array_const ("rules",
                                   &jrules),
@@ -158,8 +158,8 @@ take_aml_decision_run (void *cls,
                                      &jmeasures),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_string ("new_measure",
-                               &new_measure),
+      GNUNET_JSON_spec_string ("new_measures",
+                               &new_measures),
       NULL),
     GNUNET_JSON_spec_end ()
   };
@@ -350,13 +350,13 @@ take_aml_decision_run (void *cls,
     }
     GNUNET_assert (off == num_measures);
 
-    ds->dh = TALER_EXCHANGE_add_aml_decision (
+    ds->dh = TALER_EXCHANGE_post_aml_decision (
       TALER_TESTING_interpreter_get_context (is),
       exchange_url,
       h_payto,
       now,
       ds->successor_measure,
-      new_measure,
+      new_measures,
       expiration_time,
       num_rules,
       rules,
@@ -402,7 +402,7 @@ take_aml_decision_cleanup (void *cls,
   {
     TALER_TESTING_command_incomplete (ds->is,
                                       cmd->label);
-    TALER_EXCHANGE_add_aml_decision_cancel (ds->dh);
+    TALER_EXCHANGE_post_aml_decision_cancel (ds->dh);
     ds->dh = NULL;
   }
   json_decref (ds->new_rules);

-- 
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]