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 (17356B)


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