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


      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 
    134 
    135 /**
    136  * Function called when we're done processing the
    137  * HTTP POST /aml/$OFFICER_PUB/decision request.
    138  *
    139  * @param cls the `struct TALER_EXCHANGE_PostAmlDecisionHandle *`
    140  * @param response_code HTTP response code, 0 on error
    141  * @param response response body, NULL if not in JSON
    142  */
    143 static void
    144 handle_post_aml_decision_finished (void *cls,
    145                                    long response_code,
    146                                    const void *response)
    147 {
    148   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh = cls;
    149   const json_t *json = response;
    150   struct TALER_EXCHANGE_PostAmlDecisionResponse pr = {
    151     .hr.http_status = (unsigned int) response_code,
    152     .hr.reply = json
    153   };
    154 
    155   padh->job = NULL;
    156   switch (response_code)
    157   {
    158   case 0:
    159     pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    160     pr.hr.hint = "server offline?";
    161     break;
    162   case MHD_HTTP_NO_CONTENT:
    163     break;
    164   case MHD_HTTP_FORBIDDEN:
    165     pr.hr.ec = TALER_JSON_get_error_code (json);
    166     pr.hr.hint = TALER_JSON_get_error_hint (json);
    167     break;
    168   case MHD_HTTP_CONFLICT:
    169     pr.hr.ec = TALER_JSON_get_error_code (json);
    170     pr.hr.hint = TALER_JSON_get_error_hint (json);
    171     break;
    172   default:
    173     GNUNET_break_op (0);
    174     pr.hr.ec = TALER_JSON_get_error_code (json);
    175     pr.hr.hint = TALER_JSON_get_error_hint (json);
    176     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    177                 "Unexpected response code %u/%d for POST AML decision\n",
    178                 (unsigned int) response_code,
    179                 (int) pr.hr.ec);
    180     break;
    181   }
    182   if (NULL != padh->cb)
    183   {
    184     padh->cb (padh->cb_cls,
    185               &pr);
    186     padh->cb = NULL;
    187   }
    188   TALER_EXCHANGE_post_aml_decision_cancel (padh);
    189 }
    190 
    191 
    192 /**
    193  * Build the new_rules JSON object from rules and measures arrays.
    194  *
    195  * @param successor_measure optional successor measure name
    196  * @param expiration_time when the new rules expire
    197  * @param num_rules length of @a rules
    198  * @param rules the rules array
    199  * @param num_measures length of @a measures
    200  * @param measures the measures array
    201  * @return new JSON object (caller owns reference), NULL on error
    202  */
    203 static json_t *
    204 build_new_rules (
    205   const char *successor_measure,
    206   struct GNUNET_TIME_Timestamp expiration_time,
    207   unsigned int num_rules,
    208   const struct TALER_EXCHANGE_AccountRule rules[static num_rules],
    209   unsigned int num_measures,
    210   const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures])
    211 {
    212   json_t *jrules;
    213   json_t *jmeasures;
    214 
    215   jrules = json_array ();
    216   GNUNET_assert (NULL != jrules);
    217   for (unsigned int i = 0; i < num_rules; i++)
    218   {
    219     const struct TALER_EXCHANGE_AccountRule *al = &rules[i];
    220     json_t *ameasures;
    221     json_t *rule;
    222 
    223     ameasures = json_array ();
    224     GNUNET_assert (NULL != ameasures);
    225     for (unsigned int j = 0; j < al->num_measures; j++)
    226       GNUNET_assert (0 ==
    227                      json_array_append_new (ameasures,
    228                                             json_string (al->measures[j])));
    229     rule = GNUNET_JSON_PACK (
    230       TALER_JSON_pack_kycte ("operation_type",
    231                              al->operation_type),
    232       TALER_JSON_pack_amount ("threshold",
    233                               &al->threshold),
    234       GNUNET_JSON_pack_time_rel ("timeframe",
    235                                  al->timeframe),
    236       GNUNET_JSON_pack_array_steal ("measures",
    237                                     ameasures),
    238       GNUNET_JSON_pack_bool ("exposed",
    239                              al->exposed),
    240       GNUNET_JSON_pack_bool ("is_and_combinator",
    241                              al->is_and_combinator),
    242       GNUNET_JSON_pack_uint64 ("display_priority",
    243                                al->display_priority)
    244       );
    245     GNUNET_break (0 ==
    246                   json_array_append_new (jrules,
    247                                          rule));
    248   }
    249 
    250   jmeasures = json_object ();
    251   GNUNET_assert (NULL != jmeasures);
    252   for (unsigned int i = 0; i < num_measures; i++)
    253   {
    254     const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i];
    255     json_t *measure;
    256 
    257     measure = GNUNET_JSON_PACK (
    258       GNUNET_JSON_pack_string ("check_name",
    259                                mi->check_name),
    260       GNUNET_JSON_pack_allow_null (
    261         GNUNET_JSON_pack_string ("prog_name",
    262                                  mi->prog_name)),
    263       GNUNET_JSON_pack_allow_null (
    264         GNUNET_JSON_pack_object_incref ("context",
    265                                         (json_t *) mi->context))
    266       );
    267     GNUNET_break (0 ==
    268                   json_object_set_new (jmeasures,
    269                                        mi->measure_name,
    270                                        measure));
    271   }
    272 
    273   return GNUNET_JSON_PACK (
    274     GNUNET_JSON_pack_timestamp ("expiration_time",
    275                                 expiration_time),
    276     GNUNET_JSON_pack_allow_null (
    277       GNUNET_JSON_pack_string ("successor_measure",
    278                                successor_measure)),
    279     GNUNET_JSON_pack_array_steal ("rules",
    280                                   jrules),
    281     GNUNET_JSON_pack_object_steal ("custom_measures",
    282                                    jmeasures)
    283     );
    284 }
    285 
    286 
    287 struct TALER_EXCHANGE_PostAmlDecisionHandle *
    288 TALER_EXCHANGE_post_aml_decision_create (
    289   struct GNUNET_CURL_Context *ctx,
    290   const char *url,
    291   const struct TALER_NormalizedPaytoHashP *h_payto,
    292   struct GNUNET_TIME_Timestamp decision_time,
    293   const char *successor_measure,
    294   struct GNUNET_TIME_Timestamp expiration_time,
    295   unsigned int num_rules,
    296   const struct TALER_EXCHANGE_AccountRule rules[static num_rules],
    297   unsigned int num_measures,
    298   const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures],
    299   bool keep_investigating,
    300   const char *justification,
    301   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
    302 {
    303   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh;
    304   json_t *new_rules;
    305 
    306   new_rules = build_new_rules (successor_measure,
    307                                expiration_time,
    308                                num_rules,
    309                                rules,
    310                                num_measures,
    311                                measures);
    312   if (NULL == new_rules)
    313   {
    314     GNUNET_break (0);
    315     return NULL;
    316   }
    317 
    318   padh = GNUNET_new (struct TALER_EXCHANGE_PostAmlDecisionHandle);
    319   padh->ctx = ctx;
    320   padh->base_url = GNUNET_strdup (url);
    321   padh->h_payto = *h_payto;
    322   padh->decision_time = decision_time;
    323   padh->justification = GNUNET_strdup (justification);
    324   padh->keep_investigating = keep_investigating;
    325   padh->new_rules = new_rules;
    326   padh->officer_priv = *officer_priv;
    327   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    328                                       &padh->officer_pub.eddsa_pub);
    329   return padh;
    330 }
    331 
    332 
    333 enum GNUNET_GenericReturnValue
    334 TALER_EXCHANGE_post_aml_decision_set_options_ (
    335   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh,
    336   unsigned int num_options,
    337   const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[])
    338 {
    339   for (unsigned int i = 0; i < num_options; i++)
    340   {
    341     const struct TALER_EXCHANGE_PostAmlDecisionOptionValue *opt = &options[i];
    342 
    343     switch (opt->option)
    344     {
    345     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_END:
    346       return GNUNET_OK;
    347     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PAYTO_URI:
    348       GNUNET_free (padh->payto_uri_str);
    349       padh->payto_uri_str =
    350         (NULL != opt->details.payto_uri.full_payto)
    351         ? GNUNET_strdup (opt->details.payto_uri.full_payto)
    352         : NULL;
    353       break;
    354     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_NEW_MEASURES:
    355       GNUNET_free (padh->new_measures);
    356       padh->new_measures =
    357         (NULL != opt->details.new_measures)
    358         ? GNUNET_strdup (opt->details.new_measures)
    359         : NULL;
    360       break;
    361     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PROPERTIES:
    362       if (NULL != padh->properties)
    363         json_decref (padh->properties);
    364       padh->properties =
    365         (NULL != opt->details.properties)
    366         ? json_incref ((json_t *) opt->details.properties)
    367         : NULL;
    368       break;
    369     case TALER_EXCHANGE_POST_AML_DECISION_OPTION_EVENTS:
    370       {
    371         if (NULL != padh->jevents)
    372           json_decref (padh->jevents);
    373         if (0 == opt->details.events.num_events)
    374         {
    375           padh->jevents = NULL;
    376         }
    377         else
    378         {
    379           padh->jevents = json_array ();
    380           GNUNET_assert (NULL != padh->jevents);
    381           for (unsigned int j = 0; j < opt->details.events.num_events; j++)
    382             GNUNET_assert (0 ==
    383                            json_array_append_new (
    384                              padh->jevents,
    385                              json_string (opt->details.events.events[j])));
    386         }
    387       }
    388       break;
    389     default:
    390       GNUNET_break (0);
    391       return GNUNET_SYSERR;
    392     }
    393   }
    394   return GNUNET_OK;
    395 }
    396 
    397 
    398 enum TALER_ErrorCode
    399 TALER_EXCHANGE_post_aml_decision_start (
    400   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh,
    401   TALER_EXCHANGE_PostAmlDecisionCallback cb,
    402   TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls)
    403 {
    404   CURL *eh;
    405   struct TALER_AmlOfficerSignatureP officer_sig;
    406   json_t *body;
    407   char *path;
    408   char opus[sizeof (padh->officer_pub) * 2];
    409   char *end;
    410   struct TALER_FullPayto payto_uri_val = {
    411     .full_payto = padh->payto_uri_str
    412   };
    413 
    414   padh->cb = cb;
    415   padh->cb_cls = cb_cls;
    416   TALER_officer_aml_decision_sign (padh->justification,
    417                                    padh->decision_time,
    418                                    &padh->h_payto,
    419                                    padh->new_rules,
    420                                    padh->properties,
    421                                    padh->new_measures,
    422                                    padh->keep_investigating,
    423                                    &padh->officer_priv,
    424                                    &officer_sig);
    425 
    426   end = GNUNET_STRINGS_data_to_string (
    427     &padh->officer_pub,
    428     sizeof (padh->officer_pub),
    429     opus,
    430     sizeof (opus));
    431   *end = '\0';
    432   GNUNET_asprintf (&path,
    433                    "aml/%s/decision",
    434                    opus);
    435   padh->url = TALER_url_join (padh->base_url,
    436                               path,
    437                               NULL);
    438   GNUNET_free (path);
    439   if (NULL == padh->url)
    440   {
    441     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    442                 "Could not construct request URL.\n");
    443     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    444   }
    445 
    446   body = GNUNET_JSON_PACK (
    447     GNUNET_JSON_pack_string ("justification",
    448                              padh->justification),
    449     GNUNET_JSON_pack_data_auto ("h_payto",
    450                                 &padh->h_payto),
    451     GNUNET_JSON_pack_allow_null (
    452       TALER_JSON_pack_full_payto ("payto_uri",
    453                                   payto_uri_val)),
    454     GNUNET_JSON_pack_object_incref ("new_rules",
    455                                     padh->new_rules),
    456     GNUNET_JSON_pack_allow_null (
    457       GNUNET_JSON_pack_object_incref ("properties",
    458                                       padh->properties)),
    459     GNUNET_JSON_pack_allow_null (
    460       GNUNET_JSON_pack_string ("new_measures",
    461                                padh->new_measures)),
    462     GNUNET_JSON_pack_bool ("keep_investigating",
    463                            padh->keep_investigating),
    464     GNUNET_JSON_pack_data_auto ("officer_sig",
    465                                 &officer_sig),
    466     GNUNET_JSON_pack_timestamp ("decision_time",
    467                                 padh->decision_time),
    468     GNUNET_JSON_pack_allow_null (
    469       GNUNET_JSON_pack_array_incref ("events",
    470                                      padh->jevents))
    471     );
    472 
    473   eh = TALER_EXCHANGE_curl_easy_get_ (padh->url);
    474   if ( (NULL == eh) ||
    475        (GNUNET_OK !=
    476         TALER_curl_easy_post (&padh->post_ctx,
    477                               eh,
    478                               body)) )
    479   {
    480     GNUNET_break (0);
    481     if (NULL != eh)
    482       curl_easy_cleanup (eh);
    483     json_decref (body);
    484     GNUNET_free (padh->url);
    485     padh->url = NULL;
    486     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    487   }
    488   json_decref (body);
    489   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    490               "Requesting URL '%s'\n",
    491               padh->url);
    492   padh->job = GNUNET_CURL_job_add2 (padh->ctx,
    493                                     eh,
    494                                     padh->post_ctx.headers,
    495                                     &handle_post_aml_decision_finished,
    496                                     padh);
    497   if (NULL == padh->job)
    498   {
    499     TALER_curl_easy_post_finished (&padh->post_ctx);
    500     GNUNET_free (padh->url);
    501     padh->url = NULL;
    502     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    503   }
    504   return TALER_EC_NONE;
    505 }
    506 
    507 
    508 void
    509 TALER_EXCHANGE_post_aml_decision_cancel (
    510   struct TALER_EXCHANGE_PostAmlDecisionHandle *padh)
    511 {
    512   if (NULL != padh->job)
    513   {
    514     GNUNET_CURL_job_cancel (padh->job);
    515     padh->job = NULL;
    516   }
    517   TALER_curl_easy_post_finished (&padh->post_ctx);
    518   json_decref (padh->new_rules);
    519   if (NULL != padh->properties)
    520     json_decref (padh->properties);
    521   if (NULL != padh->jevents)
    522     json_decref (padh->jevents);
    523   GNUNET_free (padh->url);
    524   GNUNET_free (padh->base_url);
    525   GNUNET_free (padh->justification);
    526   GNUNET_free (padh->payto_uri_str);
    527   GNUNET_free (padh->new_measures);
    528   GNUNET_free (padh);
    529 }
    530 
    531 
    532 /* end of exchange_api_post-aml-OFFICER_PUB-decision.c */