exchange

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

exchange_api_lookup_aml_decisions.c (17747B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023, 2024 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_lookup_aml_decisions.c
     19  * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_exchange_service.h"
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_handle.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A GET /aml/$OFFICER_PUB/decisions Handle
     35  */
     36 struct TALER_EXCHANGE_LookupAmlDecisions
     37 {
     38 
     39   /**
     40    * The url for this request.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Handle for the request.
     46    */
     47   struct GNUNET_CURL_Job *job;
     48 
     49   /**
     50    * Function to call with the result.
     51    */
     52   TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb;
     53 
     54   /**
     55    * Closure for @e cb.
     56    */
     57   void *decisions_cb_cls;
     58 
     59   /**
     60    * HTTP headers for the job.
     61    */
     62   struct curl_slist *job_headers;
     63 
     64   /**
     65    * Array with measure information.
     66    */
     67   struct TALER_EXCHANGE_MeasureInformation *mip;
     68 
     69   /**
     70    * Array with rule information.
     71    */
     72   struct TALER_EXCHANGE_KycRule *rp;
     73 
     74   /**
     75    * Array with all the measures (of all the rules!).
     76    */
     77   const char **mp;
     78 };
     79 
     80 
     81 /**
     82  * Parse AML limits array.
     83  *
     84  * @param[in,out] lh handle to use for allocations
     85  * @param jlimits JSON array with AML rules
     86  * @param[out] ds where to write the result
     87  * @return #GNUNET_OK on success
     88  */
     89 static enum GNUNET_GenericReturnValue
     90 parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
     91               const json_t *jlimits,
     92               struct TALER_EXCHANGE_AmlDecision *ds)
     93 {
     94   struct TALER_EXCHANGE_LegitimizationRuleSet *limits
     95     = &ds->limits;
     96   const json_t *jrules;
     97   const json_t *jmeasures;
     98   size_t mip_len;
     99   size_t rule_len;
    100   size_t total;
    101   struct GNUNET_JSON_Specification spec[] = {
    102     GNUNET_JSON_spec_timestamp ("expiration_time",
    103                                 &limits->expiration_time),
    104     GNUNET_JSON_spec_mark_optional (
    105       GNUNET_JSON_spec_string ("successor_measure",
    106                                &limits->successor_measure),
    107       NULL),
    108     GNUNET_JSON_spec_array_const ("rules",
    109                                   &jrules),
    110     GNUNET_JSON_spec_object_const ("custom_measures",
    111                                    &jmeasures),
    112     GNUNET_JSON_spec_end ()
    113   };
    114 
    115   if (GNUNET_OK !=
    116       GNUNET_JSON_parse (jlimits,
    117                          spec,
    118                          NULL,
    119                          NULL))
    120   {
    121     GNUNET_break_op (0);
    122     return GNUNET_SYSERR;
    123   }
    124 
    125   mip_len = json_object_size (jmeasures);
    126   lh->mip = GNUNET_new_array (mip_len,
    127                               struct TALER_EXCHANGE_MeasureInformation);
    128   limits->measures = lh->mip;
    129   limits->measures_length = mip_len;
    130 
    131   {
    132     const char *measure_name;
    133     const json_t *jmeasure;
    134 
    135     json_object_foreach ((json_t*) jmeasures,
    136                          measure_name,
    137                          jmeasure)
    138     {
    139       struct TALER_EXCHANGE_MeasureInformation *mi
    140         = &lh->mip[--mip_len];
    141       struct GNUNET_JSON_Specification ispec[] = {
    142         GNUNET_JSON_spec_string ("check_name",
    143                                  &mi->check_name),
    144         GNUNET_JSON_spec_mark_optional (
    145           GNUNET_JSON_spec_string ("prog_name",
    146                                    &mi->prog_name),
    147           NULL),
    148         GNUNET_JSON_spec_mark_optional (
    149           GNUNET_JSON_spec_object_const ("context",
    150                                          &mi->context),
    151           NULL),
    152         GNUNET_JSON_spec_end ()
    153       };
    154 
    155       if (GNUNET_OK !=
    156           GNUNET_JSON_parse (jmeasure,
    157                              ispec,
    158                              NULL,
    159                              NULL))
    160       {
    161         GNUNET_break_op (0);
    162         return GNUNET_SYSERR;
    163       }
    164       mi->measure_name = measure_name;
    165     }
    166   }
    167 
    168   total = 0;
    169 
    170   {
    171     const json_t *rule;
    172     size_t idx;
    173 
    174     json_array_foreach ((json_t *) jrules,
    175                         idx,
    176                         rule)
    177     {
    178       total += json_array_size (json_object_get (rule,
    179                                                  "measures"));
    180     }
    181   }
    182 
    183   rule_len = json_array_size (jrules);
    184   lh->rp = GNUNET_new_array (rule_len,
    185                              struct TALER_EXCHANGE_KycRule);
    186   lh->mp = GNUNET_new_array (total,
    187                              const char *);
    188 
    189   {
    190     const json_t *rule;
    191     size_t idx;
    192 
    193     json_array_foreach ((json_t *) jrules,
    194                         idx,
    195                         rule)
    196     {
    197       const json_t *smeasures;
    198       struct TALER_EXCHANGE_KycRule *r
    199         = &lh->rp[--rule_len];
    200       struct GNUNET_JSON_Specification ispec[] = {
    201         TALER_JSON_spec_kycte ("operation_type",
    202                                &r->operation_type),
    203         TALER_JSON_spec_amount_any ("threshold",
    204                                     &r->threshold),
    205         GNUNET_JSON_spec_relative_time ("timeframe",
    206                                         &r->timeframe),
    207         GNUNET_JSON_spec_array_const ("measures",
    208                                       &smeasures),
    209         GNUNET_JSON_spec_mark_optional (
    210           GNUNET_JSON_spec_bool ("exposed",
    211                                  &r->exposed),
    212           NULL),
    213         GNUNET_JSON_spec_mark_optional (
    214           GNUNET_JSON_spec_bool ("is_and_combinator",
    215                                  &r->is_and_combinator),
    216           NULL),
    217         GNUNET_JSON_spec_uint32 ("display_priority",
    218                                  &r->display_priority),
    219         GNUNET_JSON_spec_end ()
    220       };
    221       size_t mlen;
    222 
    223       if (GNUNET_OK !=
    224           GNUNET_JSON_parse (rule,
    225                              ispec,
    226                              NULL,
    227                              NULL))
    228       {
    229         GNUNET_break_op (0);
    230         return GNUNET_SYSERR;
    231       }
    232 
    233       mlen = json_array_size (smeasures);
    234       GNUNET_assert (mlen <= total);
    235       total -= mlen;
    236 
    237       {
    238         size_t midx;
    239         const json_t *smeasure;
    240 
    241         json_array_foreach (smeasures,
    242                             midx,
    243                             smeasure)
    244         {
    245           const char *sval = json_string_value (smeasure);
    246 
    247           if (NULL == sval)
    248           {
    249             GNUNET_break_op (0);
    250             return GNUNET_SYSERR;
    251           }
    252           lh->mp[total + midx] = sval;
    253           if (0 == strcasecmp (sval,
    254                                "verboten"))
    255             r->verboten = true;
    256         }
    257       }
    258       r->measures = &lh->mp[total];
    259       r->measures_length = r->verboten ? 0 : total;
    260     }
    261   }
    262   return GNUNET_OK;
    263 }
    264 
    265 
    266 /**
    267  * Parse AML decision summary array.
    268  *
    269  * @param[in,out] lh handle to use for allocations
    270  * @param decisions JSON array with AML decision summaries
    271  * @param[out] decision_ar where to write the result
    272  * @return #GNUNET_OK on success
    273  */
    274 static enum GNUNET_GenericReturnValue
    275 parse_aml_decisions (
    276   struct TALER_EXCHANGE_LookupAmlDecisions *lh,
    277   const json_t *decisions,
    278   struct TALER_EXCHANGE_AmlDecision *decision_ar)
    279 {
    280   json_t *obj;
    281   size_t idx;
    282 
    283   json_array_foreach (decisions, idx, obj)
    284   {
    285     struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx];
    286     const json_t *jlimits;
    287     struct GNUNET_JSON_Specification spec[] = {
    288       GNUNET_JSON_spec_fixed_auto ("h_payto",
    289                                    &decision->h_payto),
    290       GNUNET_JSON_spec_uint64 ("rowid",
    291                                &decision->rowid),
    292       GNUNET_JSON_spec_mark_optional (
    293         GNUNET_JSON_spec_string ("justification",
    294                                  &decision->justification),
    295         NULL),
    296       GNUNET_JSON_spec_timestamp ("decision_time",
    297                                   &decision->decision_time),
    298       GNUNET_JSON_spec_mark_optional (
    299         GNUNET_JSON_spec_object_const ("properties",
    300                                        &decision->jproperties),
    301         NULL),
    302       GNUNET_JSON_spec_object_const ("limits",
    303                                      &jlimits),
    304       GNUNET_JSON_spec_bool ("to_investigate",
    305                              &decision->to_investigate),
    306       GNUNET_JSON_spec_bool ("is_active",
    307                              &decision->is_active),
    308       GNUNET_JSON_spec_end ()
    309     };
    310 
    311     if (GNUNET_OK !=
    312         GNUNET_JSON_parse (obj,
    313                            spec,
    314                            NULL,
    315                            NULL))
    316     {
    317       GNUNET_break_op (0);
    318       return GNUNET_SYSERR;
    319     }
    320     if (GNUNET_OK !=
    321         parse_limits (lh,
    322                       jlimits,
    323                       decision))
    324     {
    325       GNUNET_break_op (0);
    326       return GNUNET_SYSERR;
    327     }
    328   }
    329   return GNUNET_OK;
    330 }
    331 
    332 
    333 /**
    334  * Parse the provided decision data from the "200 OK" response.
    335  *
    336  * @param[in,out] lh handle (callback may be zero'ed out)
    337  * @param json json reply with the data for one coin
    338  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    339  */
    340 static enum GNUNET_GenericReturnValue
    341 parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
    342                     const json_t *json)
    343 {
    344   struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
    345     .hr.reply = json,
    346     .hr.http_status = MHD_HTTP_OK
    347   };
    348   const json_t *records;
    349   struct GNUNET_JSON_Specification spec[] = {
    350     GNUNET_JSON_spec_array_const ("records",
    351                                   &records),
    352     GNUNET_JSON_spec_end ()
    353   };
    354 
    355   if (GNUNET_OK !=
    356       GNUNET_JSON_parse (json,
    357                          spec,
    358                          NULL,
    359                          NULL))
    360   {
    361     GNUNET_break_op (0);
    362     return GNUNET_SYSERR;
    363   }
    364   lr.details.ok.decisions_length
    365     = json_array_size (records);
    366   {
    367     struct TALER_EXCHANGE_AmlDecision decisions[
    368       GNUNET_NZL (lr.details.ok.decisions_length)];
    369     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    370 
    371     memset (decisions,
    372             0,
    373             sizeof (decisions));
    374     lr.details.ok.decisions = decisions;
    375     ret = parse_aml_decisions (lh,
    376                                records,
    377                                decisions);
    378     if (GNUNET_OK == ret)
    379     {
    380       lh->decisions_cb (lh->decisions_cb_cls,
    381                         &lr);
    382       lh->decisions_cb = NULL;
    383     }
    384     GNUNET_free (lh->mip);
    385     GNUNET_free (lh->rp);
    386     GNUNET_free (lh->mp);
    387     return ret;
    388   }
    389 }
    390 
    391 
    392 /**
    393  * Function called when we're done processing the
    394  * HTTP /aml/$OFFICER_PUB/decisions request.
    395  *
    396  * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions`
    397  * @param response_code HTTP response code, 0 on error
    398  * @param response parsed JSON result, NULL on error
    399  */
    400 static void
    401 handle_lookup_finished (void *cls,
    402                         long response_code,
    403                         const void *response)
    404 {
    405   struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls;
    406   const json_t *j = response;
    407   struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
    408     .hr.reply = j,
    409     .hr.http_status = (unsigned int) response_code
    410   };
    411 
    412   lh->job = NULL;
    413   switch (response_code)
    414   {
    415   case 0:
    416     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    417     break;
    418   case MHD_HTTP_OK:
    419     if (GNUNET_OK !=
    420         parse_decisions_ok (lh,
    421                             j))
    422     {
    423       GNUNET_break_op (0);
    424       lr.hr.http_status = 0;
    425       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    426       break;
    427     }
    428     GNUNET_assert (NULL == lh->decisions_cb);
    429     TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
    430     return;
    431   case MHD_HTTP_NO_CONTENT:
    432     break;
    433   case MHD_HTTP_BAD_REQUEST:
    434     json_dumpf (j,
    435                 stderr,
    436                 JSON_INDENT (2));
    437     lr.hr.ec = TALER_JSON_get_error_code (j);
    438     lr.hr.hint = TALER_JSON_get_error_hint (j);
    439     /* This should never happen, either us or the exchange is buggy
    440        (or API version conflict); just pass JSON reply to the application */
    441     break;
    442   case MHD_HTTP_FORBIDDEN:
    443     lr.hr.ec = TALER_JSON_get_error_code (j);
    444     lr.hr.hint = TALER_JSON_get_error_hint (j);
    445     /* Nothing really to verify, exchange says this coin was not melted; we
    446        should pass the JSON reply to the application */
    447     break;
    448   case MHD_HTTP_NOT_FOUND:
    449     lr.hr.ec = TALER_JSON_get_error_code (j);
    450     lr.hr.hint = TALER_JSON_get_error_hint (j);
    451     /* Nothing really to verify, exchange says this coin was not melted; we
    452        should pass the JSON reply to the application */
    453     break;
    454   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    455     lr.hr.ec = TALER_JSON_get_error_code (j);
    456     lr.hr.hint = TALER_JSON_get_error_hint (j);
    457     /* Server had an internal issue; we should retry, but this API
    458        leaves this to the application */
    459     break;
    460   default:
    461     /* unexpected response code */
    462     GNUNET_break_op (0);
    463     lr.hr.ec = TALER_JSON_get_error_code (j);
    464     lr.hr.hint = TALER_JSON_get_error_hint (j);
    465     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    466                 "Unexpected response code %u/%d for lookup AML decisions\n",
    467                 (unsigned int) response_code,
    468                 (int) lr.hr.ec);
    469     break;
    470   }
    471   if (NULL != lh->decisions_cb)
    472     lh->decisions_cb (lh->decisions_cb_cls,
    473                       &lr);
    474   TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
    475 }
    476 
    477 
    478 struct TALER_EXCHANGE_LookupAmlDecisions *
    479 TALER_EXCHANGE_lookup_aml_decisions (
    480   struct GNUNET_CURL_Context *ctx,
    481   const char *exchange_url,
    482   const struct TALER_NormalizedPaytoHashP *h_payto,
    483   enum TALER_EXCHANGE_YesNoAll investigation_only,
    484   enum TALER_EXCHANGE_YesNoAll active_only,
    485   uint64_t offset,
    486   int64_t limit,
    487   const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
    488   TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
    489   void *cb_cls)
    490 {
    491   struct TALER_EXCHANGE_LookupAmlDecisions *lh;
    492   CURL *eh;
    493   struct TALER_AmlOfficerPublicKeyP officer_pub;
    494   struct TALER_AmlOfficerSignatureP officer_sig;
    495   char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2
    496                + 32];
    497 
    498   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    499                                       &officer_pub.eddsa_pub);
    500   TALER_officer_aml_query_sign (officer_priv,
    501                                 &officer_sig);
    502   {
    503     char pub_str[sizeof (officer_pub) * 2];
    504     char *end;
    505 
    506     end = GNUNET_STRINGS_data_to_string (
    507       &officer_pub,
    508       sizeof (officer_pub),
    509       pub_str,
    510       sizeof (pub_str));
    511     *end = '\0';
    512     GNUNET_snprintf (arg_str,
    513                      sizeof (arg_str),
    514                      "aml/%s/decisions",
    515                      pub_str);
    516   }
    517   lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
    518   lh->decisions_cb = cb;
    519   lh->decisions_cb_cls = cb_cls;
    520   {
    521     char limit_s[24];
    522     char offset_s[24];
    523     char payto_s[sizeof (*h_payto) * 2];
    524     char *end;
    525 
    526     if (NULL != h_payto)
    527     {
    528       end = GNUNET_STRINGS_data_to_string (
    529         h_payto,
    530         sizeof (*h_payto),
    531         payto_s,
    532         sizeof (payto_s));
    533       *end = '\0';
    534     }
    535     GNUNET_snprintf (limit_s,
    536                      sizeof (limit_s),
    537                      "%lld",
    538                      (long long) limit);
    539     GNUNET_snprintf (offset_s,
    540                      sizeof (offset_s),
    541                      "%llu",
    542                      (unsigned long long) offset);
    543     lh->url = TALER_url_join (
    544       exchange_url,
    545       arg_str,
    546       "limit",
    547       limit_s,
    548       "offset",
    549       ( ( (limit < 0) && (UINT64_MAX == offset) ) ||
    550         ( (limit > 0) && (0 == offset) ) )
    551       ? NULL
    552       : offset_s,
    553       "h_payto",
    554       NULL != h_payto
    555       ? payto_s
    556       : NULL,
    557       "active",
    558       TALER_EXCHANGE_YNA_ALL != active_only
    559       ? TALER_yna_to_string (active_only)
    560       : NULL,
    561       "investigation",
    562       TALER_EXCHANGE_YNA_ALL != investigation_only
    563       ? TALER_yna_to_string (investigation_only)
    564       : NULL,
    565       NULL);
    566   }
    567   if (NULL == lh->url)
    568   {
    569     GNUNET_free (lh);
    570     return NULL;
    571   }
    572   eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
    573   if (NULL == eh)
    574   {
    575     GNUNET_break (0);
    576     GNUNET_free (lh->url);
    577     GNUNET_free (lh);
    578     return NULL;
    579   }
    580   {
    581     char *hdr;
    582     char sig_str[sizeof (officer_sig) * 2];
    583     char *end;
    584 
    585     end = GNUNET_STRINGS_data_to_string (
    586       &officer_sig,
    587       sizeof (officer_sig),
    588       sig_str,
    589       sizeof (sig_str));
    590     *end = '\0';
    591 
    592     GNUNET_asprintf (&hdr,
    593                      "%s: %s",
    594                      TALER_AML_OFFICER_SIGNATURE_HEADER,
    595                      sig_str);
    596     lh->job_headers = curl_slist_append (NULL,
    597                                          hdr);
    598     GNUNET_free (hdr);
    599     lh->job_headers = curl_slist_append (lh->job_headers,
    600                                          "Content-type: application/json");
    601     lh->job = GNUNET_CURL_job_add2 (ctx,
    602                                     eh,
    603                                     lh->job_headers,
    604                                     &handle_lookup_finished,
    605                                     lh);
    606   }
    607   return lh;
    608 }
    609 
    610 
    611 void
    612 TALER_EXCHANGE_lookup_aml_decisions_cancel (
    613   struct TALER_EXCHANGE_LookupAmlDecisions *lh)
    614 {
    615   if (NULL != lh->job)
    616   {
    617     GNUNET_CURL_job_cancel (lh->job);
    618     lh->job = NULL;
    619   }
    620   curl_slist_free_all (lh->job_headers);
    621   GNUNET_free (lh->url);
    622   GNUNET_free (lh);
    623 }
    624 
    625 
    626 /* end of exchange_api_lookup_aml_decisions.c */