exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_post-aml-OFFICER_PUB-decision.c (17453B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023, 2024, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_post-aml-OFFICER_PUB-decision.c
     19  * @brief functions to add an AML decision by an AML officer
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/taler_json_lib.h"
     23 #include <microhttpd.h>
     24 #include <gnunet/gnunet_curl_lib.h>
     25 #include "taler/exchange/post-aml-OFFICER_PUB-decision.h"
     26 #include "exchange_api_curl_defaults.h"
     27 #include "taler/taler_signatures.h"
     28 #include "taler/taler_curl_lib.h"
     29 
     30 
     31 /**
     32  * @brief A POST /aml/$OFFICER_PUB/decision Handle
     33  */
     34 struct TALER_EXCHANGE_PostAmlDecisionHandle
     35 {
     36 
     37   /**
     38    * The base URL of the exchange.
     39    */
     40   char *base_url;
     41 
     42   /**
     43    * The full URL for this request.
     44    */
     45   char *url;
     46 
     47   /**
     48    * Minor context that holds body and headers.
     49    */
     50   struct TALER_CURL_PostContext post_ctx;
     51 
     52   /**
     53    * Handle for the request.
     54    */
     55   struct GNUNET_CURL_Job *job;
     56 
     57   /**
     58    * Function to call with the result.
     59    */
     60   TALER_EXCHANGE_PostAmlDecisionCallback cb;
     61 
     62   /**
     63    * Closure for @e cb.
     64    */
     65   TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls;
     66 
     67   /**
     68    * Reference to the execution context.
     69    */
     70   struct GNUNET_CURL_Context *ctx;
     71 
     72   /**
     73    * Public key of the AML officer.
     74    */
     75   struct TALER_AmlOfficerPublicKeyP officer_pub;
     76 
     77   /**
     78    * Private key of the AML officer.
     79    */
     80   struct TALER_AmlOfficerPrivateKeyP officer_priv;
     81 
     82   /**
     83    * Hash of the payto URI of the account the decision is about.
     84    */
     85   struct TALER_NormalizedPaytoHashP h_payto;
     86 
     87   /**
     88    * When was the decision made.
     89    */
     90   struct GNUNET_TIME_Timestamp decision_time;
     91 
     92   /**
     93    * Human-readable justification.
     94    */
     95   char *justification;
     96 
     97   /**
     98    * True to keep the investigation open.
     99    */
    100   bool keep_investigating;
    101 
    102   /**
    103    * Pre-built new_rules JSON object.
    104    */
    105   json_t *new_rules;
    106 
    107   /**
    108    * Optional: full payto URI string, may be NULL.
    109    */
    110   char *payto_uri_str;
    111 
    112   /**
    113    * Optional: space-separated list of measures to trigger immediately
    114    * (from options, may be NULL).
    115    */
    116   char *new_measures;
    117 
    118   /**
    119    * Optional: JSON object with account properties
    120    * (from options, may be NULL).
    121    */
    122   json_t *properties;
    123 
    124   /**
    125    * Optional: JSON array of events to trigger
    126    * (from options; may be NULL).
    127    */
    128   json_t *jevents;
    129 
    130   /**
    131    * Optional: JSON object with KYC attributes
    132    * (from options; may be NULL).
    133    */
    134   json_t *attributes;
    135 
    136   /**
    137    * Optional: expiration time for KYC attributes.
    138    * Only meaningful if @e attributes is non-NULL.
    139    */
    140   struct GNUNET_TIME_Timestamp attributes_expiration;
    141 
    142 };
    143 
    144 
    145 /**
    146  * Function called when we're done processing the
    147  * HTTP POST /aml/$OFFICER_PUB/decision request.
    148  *
    149  * @param cls the `struct TALER_EXCHANGE_PostAmlDecisionHandle *`
    150  * @param response_code HTTP response code, 0 on error
    151  * @param response response body, NULL if not in JSON
    152  */
    153 static void
    154 handle_post_aml_decision_finished (void *cls,
    155                                    long response_code,
    156                                    const void *response)
    157 {
    158   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh = cls;
    159   const json_t *json = response;
    160   struct TALER_EXCHANGE_PostAmlDecisionResponse pr = {
    161     .hr.http_status = (unsigned int) response_code,
    162     .hr.reply = json
    163   };
    164 
    165   padh->job = NULL;
    166   switch (response_code)
    167   {
    168   case 0:
    169     pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    170     pr.hr.hint = "server offline?";
    171     break;
    172   case MHD_HTTP_NO_CONTENT:
    173     break;
    174   case MHD_HTTP_FORBIDDEN:
    175     pr.hr.ec = TALER_JSON_get_error_code (json);
    176     pr.hr.hint = TALER_JSON_get_error_hint (json);
    177     break;
    178   case MHD_HTTP_CONFLICT:
    179     pr.hr.ec = TALER_JSON_get_error_code (json);
    180     pr.hr.hint = TALER_JSON_get_error_hint (json);
    181     break;
    182   default:
    183     GNUNET_break_op (0);
    184     pr.hr.ec = TALER_JSON_get_error_code (json);
    185     pr.hr.hint = TALER_JSON_get_error_hint (json);
    186     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    187                 "Unexpected response code %u/%d for POST AML decision\n",
    188                 (unsigned int) response_code,
    189                 (int) pr.hr.ec);
    190     break;
    191   }
    192   if (NULL != padh->cb)
    193   {
    194     padh->cb (padh->cb_cls,
    195               &pr);
    196     padh->cb = NULL;
    197   }
    198   TALER_EXCHANGE_post_aml_decision_cancel (padh);
    199 }
    200 
    201 
    202 /**
    203  * Build the new_rules JSON object from rules and measures arrays.
    204  *
    205  * @param successor_measure optional successor measure name
    206  * @param expiration_time when the new rules expire
    207  * @param num_rules length of @a rules
    208  * @param rules the rules array
    209  * @param num_measures length of @a measures
    210  * @param measures the measures array
    211  * @return new JSON object (caller owns reference), NULL on error
    212  */
    213 static json_t *
    214 build_new_rules (
    215   const char *successor_measure,
    216   struct GNUNET_TIME_Timestamp expiration_time,
    217   unsigned int num_rules,
    218   const struct TALER_EXCHANGE_AccountRule rules[static num_rules],
    219   unsigned int num_measures,
    220   const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures])
    221 {
    222   json_t *jrules;
    223   json_t *jmeasures;
    224 
    225   jrules = json_array ();
    226   GNUNET_assert (NULL != jrules);
    227   for (unsigned int i = 0; i < num_rules; i++)
    228   {
    229     const struct TALER_EXCHANGE_AccountRule *al = &rules[i];
    230     json_t *ameasures;
    231     json_t *rule;
    232 
    233     ameasures = json_array ();
    234     GNUNET_assert (NULL != ameasures);
    235     for (unsigned int j = 0; j < al->num_measures; j++)
    236       GNUNET_assert (0 ==
    237                      json_array_append_new (ameasures,
    238                                             json_string (al->measures[j])));
    239     rule = GNUNET_JSON_PACK (
    240       TALER_JSON_pack_kycte ("operation_type",
    241                              al->operation_type),
    242       TALER_JSON_pack_amount ("threshold",
    243                               &al->threshold),
    244       GNUNET_JSON_pack_time_rel ("timeframe",
    245                                  al->timeframe),
    246       GNUNET_JSON_pack_array_steal ("measures",
    247                                     ameasures),
    248       GNUNET_JSON_pack_allow_null (
    249         GNUNET_JSON_pack_string ("rule_name",
    250                                  al->rule_name)),
    251       GNUNET_JSON_pack_bool ("exposed",
    252                              al->exposed),
    253       GNUNET_JSON_pack_bool ("is_and_combinator",
    254                              al->is_and_combinator),
    255       GNUNET_JSON_pack_uint64 ("display_priority",
    256                                al->display_priority)
    257       );
    258     GNUNET_break (0 ==
    259                   json_array_append_new (jrules,
    260                                          rule));
    261   }
    262 
    263   jmeasures = json_object ();
    264   GNUNET_assert (NULL != jmeasures);
    265   for (unsigned int i = 0; i < num_measures; i++)
    266   {
    267     const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i];
    268     json_t *measure;
    269 
    270     measure = GNUNET_JSON_PACK (
    271       GNUNET_JSON_pack_string ("check_name",
    272                                mi->check_name),
    273       GNUNET_JSON_pack_allow_null (
    274         GNUNET_JSON_pack_string ("prog_name",
    275                                  mi->prog_name)),
    276       /* We pack "NONE" for unknown, NULL would also be OK in that case. */
    277       TALER_JSON_pack_kycte ("operation_type",
    278                              mi->operation_type),
    279       GNUNET_JSON_pack_allow_null (
    280         GNUNET_JSON_pack_object_incref ("context",
    281                                         (json_t *) mi->context))
    282       );
    283     GNUNET_break (0 ==
    284                   json_object_set_new (jmeasures,
    285                                        mi->measure_name,
    286                                        measure));
    287   }
    288 
    289   return GNUNET_JSON_PACK (
    290     GNUNET_JSON_pack_timestamp ("expiration_time",
    291                                 expiration_time),
    292     GNUNET_JSON_pack_allow_null (
    293       GNUNET_JSON_pack_string ("successor_measure",
    294                                successor_measure)),
    295     GNUNET_JSON_pack_array_steal ("rules",
    296                                   jrules),
    297     GNUNET_JSON_pack_object_steal ("custom_measures",
    298                                    jmeasures)
    299     );
    300 }
    301 
    302 
    303 struct TALER_EXCHANGE_PostAmlDecisionHandle *
    304 TALER_EXCHANGE_post_aml_decision_create (
    305   struct GNUNET_CURL_Context *ctx,
    306   const char *url,
    307   const struct TALER_NormalizedPaytoHashP *h_payto,
    308   struct GNUNET_TIME_Timestamp decision_time,
    309   const char *successor_measure,
    310   struct GNUNET_TIME_Timestamp expiration_time,
    311   unsigned int num_rules,
    312   const struct TALER_EXCHANGE_AccountRule rules[static num_rules],
    313   unsigned int num_measures,
    314   const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures],
    315   bool keep_investigating,
    316   const char *justification,
    317   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
    318 {
    319   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh;
    320   json_t *new_rules;
    321 
    322   new_rules = build_new_rules (successor_measure,
    323                                expiration_time,
    324                                num_rules,
    325                                rules,
    326                                num_measures,
    327                                measures);
    328   if (NULL == new_rules)
    329   {
    330     GNUNET_break (0);
    331     return NULL;
    332   }
    333 
    334   padh = GNUNET_new (struct TALER_EXCHANGE_PostAmlDecisionHandle);
    335   padh->ctx = ctx;
    336   padh->base_url = GNUNET_strdup (url);
    337   padh->h_payto = *h_payto;
    338   padh->decision_time = decision_time;
    339   padh->justification = GNUNET_strdup (justification);
    340   padh->keep_investigating = keep_investigating;
    341   padh->new_rules = new_rules;
    342   padh->officer_priv = *officer_priv;
    343   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    344                                       &padh->officer_pub.eddsa_pub);
    345   return padh;
    346 }
    347 
    348 
    349 enum GNUNET_GenericReturnValue
    350 TALER_EXCHANGE_post_aml_decision_set_options_ (
    351   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh,
    352   unsigned int num_options,
    353   const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[])
    354 {
    355   for (unsigned int i = 0; i < num_options; i++)
    356   {
    357     const struct TALER_EXCHANGE_PostAmlDecisionOptionValue *opt = &options[i];
    358 
    359     switch (opt->option)
    360     {
    361     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_END:
    362       return GNUNET_OK;
    363     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PAYTO_URI:
    364       GNUNET_free (padh->payto_uri_str);
    365       padh->payto_uri_str =
    366         (NULL != opt->details.payto_uri.full_payto)
    367         ? GNUNET_strdup (opt->details.payto_uri.full_payto)
    368         : NULL;
    369       break;
    370     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_NEW_MEASURES:
    371       GNUNET_free (padh->new_measures);
    372       padh->new_measures =
    373         (NULL != opt->details.new_measures)
    374         ? GNUNET_strdup (opt->details.new_measures)
    375         : NULL;
    376       break;
    377     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PROPERTIES:
    378       if (NULL != padh->properties)
    379         json_decref (padh->properties);
    380       padh->properties =
    381         (NULL != opt->details.properties)
    382         ? json_incref ((json_t *) opt->details.properties)
    383         : NULL;
    384       break;
    385     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_EVENTS:
    386       {
    387         if (NULL != padh->jevents)
    388           json_decref (padh->jevents);
    389         if (0 == opt->details.events.num_events)
    390         {
    391           padh->jevents = NULL;
    392         }
    393         else
    394         {
    395           padh->jevents = json_array ();
    396           GNUNET_assert (NULL != padh->jevents);
    397           for (unsigned int j = 0; j < opt->details.events.num_events; j++)
    398             GNUNET_assert (0 ==
    399                            json_array_append_new (
    400                              padh->jevents,
    401                              json_string (opt->details.events.events[j])));
    402         }
    403       }
    404       break;
    405     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_ATTRIBUTES:
    406       if (NULL != padh->attributes)
    407         json_decref (padh->attributes);
    408       padh->attributes =
    409         (NULL != opt->details.attributes)
    410         ? json_incref ((json_t *) opt->details.attributes)
    411         : NULL;
    412       break;
    413     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_ATTRIBUTES_EXPIRATION:
    414       padh->attributes_expiration = opt->details.attributes_expiration;
    415       break;
    416     default:
    417       GNUNET_break (0);
    418       return GNUNET_SYSERR;
    419     }
    420   }
    421   return GNUNET_OK;
    422 }
    423 
    424 
    425 enum TALER_ErrorCode
    426 TALER_EXCHANGE_post_aml_decision_start (
    427   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh,
    428   TALER_EXCHANGE_PostAmlDecisionCallback cb,
    429   TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls)
    430 {
    431   CURL *eh;
    432   struct TALER_AmlOfficerSignatureP officer_sig;
    433   json_t *body;
    434   char *path;
    435   char opus[sizeof (padh->officer_pub) * 2];
    436   char *end;
    437   struct TALER_FullPayto payto_uri_val = {
    438     .full_payto = padh->payto_uri_str
    439   };
    440 
    441   padh->cb = cb;
    442   padh->cb_cls = cb_cls;
    443   TALER_officer_aml_decision_sign (padh->justification,
    444                                    padh->decision_time,
    445                                    &padh->h_payto,
    446                                    padh->new_rules,
    447                                    padh->properties,
    448                                    padh->new_measures,
    449                                    padh->keep_investigating,
    450                                    &padh->officer_priv,
    451                                    &officer_sig);
    452 
    453   end = GNUNET_STRINGS_data_to_string (
    454     &padh->officer_pub,
    455     sizeof (padh->officer_pub),
    456     opus,
    457     sizeof (opus));
    458   *end = '\0';
    459   GNUNET_asprintf (&path,
    460                    "aml/%s/decision",
    461                    opus);
    462   padh->url = TALER_url_join (padh->base_url,
    463                               path,
    464                               NULL);
    465   GNUNET_free (path);
    466   if (NULL == padh->url)
    467   {
    468     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    469                 "Could not construct request URL.\n");
    470     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    471   }
    472 
    473   body = GNUNET_JSON_PACK (
    474     GNUNET_JSON_pack_string ("justification",
    475                              padh->justification),
    476     GNUNET_JSON_pack_data_auto ("h_payto",
    477                                 &padh->h_payto),
    478     GNUNET_JSON_pack_allow_null (
    479       TALER_JSON_pack_full_payto ("payto_uri",
    480                                   payto_uri_val)),
    481     GNUNET_JSON_pack_object_incref ("new_rules",
    482                                     padh->new_rules),
    483     GNUNET_JSON_pack_allow_null (
    484       GNUNET_JSON_pack_object_incref ("properties",
    485                                       padh->properties)),
    486     GNUNET_JSON_pack_allow_null (
    487       GNUNET_JSON_pack_string ("new_measures",
    488                                padh->new_measures)),
    489     GNUNET_JSON_pack_bool ("keep_investigating",
    490                            padh->keep_investigating),
    491     GNUNET_JSON_pack_data_auto ("officer_sig",
    492                                 &officer_sig),
    493     GNUNET_JSON_pack_timestamp ("decision_time",
    494                                 padh->decision_time),
    495     GNUNET_JSON_pack_allow_null (
    496       GNUNET_JSON_pack_array_incref ("events",
    497                                      padh->jevents)),
    498     GNUNET_JSON_pack_allow_null (
    499       GNUNET_JSON_pack_object_incref ("attributes",
    500                                       padh->attributes))
    501     );
    502   if (NULL != padh->attributes)
    503   {
    504     GNUNET_assert (
    505       0 ==
    506       json_object_set_new (
    507         body,
    508         "attributes_expiration",
    509         GNUNET_JSON_from_timestamp (padh->attributes_expiration)));
    510   }
    511 
    512   eh = TALER_EXCHANGE_curl_easy_get_ (padh->url);
    513   if ( (NULL == eh) ||
    514        (GNUNET_OK !=
    515         TALER_curl_easy_post (&padh->post_ctx,
    516                               eh,
    517                               body)) )
    518   {
    519     GNUNET_break (0);
    520     if (NULL != eh)
    521       curl_easy_cleanup (eh);
    522     json_decref (body);
    523     GNUNET_free (padh->url);
    524     padh->url = NULL;
    525     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    526   }
    527   json_decref (body);
    528   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    529               "Requesting URL '%s'\n",
    530               padh->url);
    531   padh->job = GNUNET_CURL_job_add2 (padh->ctx,
    532                                     eh,
    533                                     padh->post_ctx.headers,
    534                                     &handle_post_aml_decision_finished,
    535                                     padh);
    536   if (NULL == padh->job)
    537   {
    538     TALER_curl_easy_post_finished (&padh->post_ctx);
    539     GNUNET_free (padh->url);
    540     padh->url = NULL;
    541     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    542   }
    543   return TALER_EC_NONE;
    544 }
    545 
    546 
    547 void
    548 TALER_EXCHANGE_post_aml_decision_cancel (
    549   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh)
    550 {
    551   if (NULL != padh->job)
    552   {
    553     GNUNET_CURL_job_cancel (padh->job);
    554     padh->job = NULL;
    555   }
    556   TALER_curl_easy_post_finished (&padh->post_ctx);
    557   json_decref (padh->new_rules);
    558   if (NULL != padh->properties)
    559     json_decref (padh->properties);
    560   if (NULL != padh->jevents)
    561     json_decref (padh->jevents);
    562   if (NULL != padh->attributes)
    563     json_decref (padh->attributes);
    564   GNUNET_free (padh->url);
    565   GNUNET_free (padh->base_url);
    566   GNUNET_free (padh->justification);
    567   GNUNET_free (padh->payto_uri_str);
    568   GNUNET_free (padh->new_measures);
    569   GNUNET_free (padh);
    570 }
    571 
    572 
    573 /* end of exchange_api_post-aml-OFFICER_PUB-decision.c */