LCOV - code coverage report
Current view: top level - lib - exchange_api_aml_legitimizations_get.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 107 172 62.2 %
Date: 2025-06-22 12:09:43 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2025 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_aml_legitimizations_get.c
      19             :  * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "taler/platform.h"
      23             : #include <jansson.h>
      24             : #include <microhttpd.h> /* just for HTTP status codes */
      25             : #include <gnunet/gnunet_util_lib.h>
      26             : #include <gnunet/gnunet_json_lib.h>
      27             : #include <gnunet/gnunet_curl_lib.h>
      28             : #include "taler/taler_exchange_service.h"
      29             : #include "taler/taler_json_lib.h"
      30             : #include "exchange_api_handle.h"
      31             : #include "taler/taler_signatures.h"
      32             : #include "exchange_api_curl_defaults.h"
      33             : 
      34             : 
      35             : /**
      36             :  * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations.
      37             :  */
      38             : struct TALER_EXCHANGE_AmlLegitimizationsGetHandle
      39             : {
      40             : 
      41             :   /**
      42             :    * The exchange base URL for this request.
      43             :    */
      44             :   char *exchange_base_url;
      45             : 
      46             :   /**
      47             :    * Our execution context.
      48             :    */
      49             :   struct GNUNET_CURL_Context *ctx;
      50             : 
      51             :   /**
      52             :    * Handle for the request.
      53             :    */
      54             :   struct GNUNET_CURL_Job *job;
      55             : 
      56             :   /**
      57             :    * Signature of the AML officer.
      58             :    */
      59             :   struct TALER_AmlOfficerSignatureP officer_sig;
      60             : 
      61             :   /**
      62             :    * Public key of the AML officer.
      63             :    */
      64             :   struct TALER_AmlOfficerPublicKeyP officer_pub;
      65             : 
      66             :   /**
      67             :    * Function to call with the result.
      68             :    */
      69             :   TALER_EXCHANGE_AmlLegitimizationsGetCallback cb;
      70             : 
      71             :   /**
      72             :    * Closure for @a cb.
      73             :    */
      74             :   TALER_EXCHANGE__AML_LEGITIMIZATIONS_GET_RESULT_CLOSURE *cb_cls;
      75             : 
      76             :   /**
      77             :    * The url for this request.
      78             :    */
      79             :   char *url;
      80             : 
      81             :   /**
      82             :    * HTTP headers for the job.
      83             :    */
      84             :   struct curl_slist *job_headers;
      85             : 
      86             :   /**
      87             :    * Request options.
      88             :    */
      89             :   struct
      90             :   {
      91             :     /**
      92             :      * Limit on number of results.
      93             :      */
      94             :     int64_t limit;
      95             : 
      96             :     /**
      97             :      * Row offset from which to return results.
      98             :      */
      99             :     uint64_t offset;
     100             : 
     101             :     /**
     102             :      * Hash of payto URI to filter by, NULL for no filter.
     103             :      */
     104             :     const struct TALER_NormalizedPaytoHashP *h_payto;
     105             : 
     106             :     /**
     107             :      * Activity filter.
     108             :      */
     109             :     enum TALER_EXCHANGE_YesNoAll active;
     110             : 
     111             :   } options;
     112             : 
     113             : };
     114             : 
     115             : 
     116             : /**
     117             :  * Parse a single measure details entry from JSON.
     118             :  *
     119             :  * @param md_json JSON object to parse
     120             :  * @param[out] md where to store the result
     121             :  * @return #GNUNET_OK on success
     122             :  */
     123             : static enum GNUNET_GenericReturnValue
     124           1 : parse_measure_details (
     125             :   const json_t *md_json,
     126             :   struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *md)
     127             : {
     128             :   struct GNUNET_JSON_Specification spec[] = {
     129           1 :     GNUNET_JSON_spec_fixed_auto ("h_payto",
     130             :                                  &md->h_payto),
     131           1 :     GNUNET_JSON_spec_uint64 ("rowid",
     132             :                              &md->rowid),
     133           1 :     GNUNET_JSON_spec_timestamp ("start_time",
     134             :                                 &md->start_time),
     135           1 :     GNUNET_JSON_spec_object_const ("measures",
     136             :                                    &md->measures),
     137           1 :     GNUNET_JSON_spec_bool ("is_finished",
     138             :                            &md->is_finished),
     139           1 :     GNUNET_JSON_spec_end ()
     140             :   };
     141             : 
     142           1 :   if (GNUNET_OK !=
     143           1 :       GNUNET_JSON_parse (md_json,
     144             :                          spec,
     145             :                          NULL,
     146             :                          NULL))
     147             :   {
     148           0 :     GNUNET_break_op (0);
     149           0 :     return GNUNET_SYSERR;
     150             :   }
     151           1 :   return GNUNET_OK;
     152             : }
     153             : 
     154             : 
     155             : /**
     156             :  * We received an #MHD_HTTP_OK status code. Handle the JSON
     157             :  * response.
     158             :  *
     159             :  * @param algh handle of the request
     160             :  * @param j JSON response
     161             :  * @return #GNUNET_OK on success
     162             :  */
     163             : static enum GNUNET_GenericReturnValue
     164           1 : handle_aml_legitimizations_get_ok (
     165             :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
     166             :   const json_t *j)
     167             : {
     168           1 :   struct TALER_EXCHANGE_AmlLegitimizationsGetResult result = {
     169             :     .hr.reply = j,
     170             :     .hr.http_status = MHD_HTTP_OK
     171             :   };
     172             :   const json_t *measures_array;
     173             :   struct GNUNET_JSON_Specification spec[] = {
     174           1 :     GNUNET_JSON_spec_array_const ("measures",
     175             :                                   &measures_array),
     176           1 :     GNUNET_JSON_spec_end ()
     177             :   };
     178             :   struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *measures;
     179             : 
     180           1 :   if (GNUNET_OK !=
     181           1 :       GNUNET_JSON_parse (j,
     182             :                          spec,
     183             :                          NULL,
     184             :                          NULL))
     185             :   {
     186           0 :     GNUNET_break_op (0);
     187           0 :     return GNUNET_SYSERR;
     188             :   }
     189             : 
     190           1 :   result.details.ok.measures_length = json_array_size (measures_array);
     191           1 :   if (0 == result.details.ok.measures_length)
     192             :   {
     193           0 :     measures = NULL;
     194             :   }
     195             :   else
     196             :   {
     197             :     measures
     198           1 :       = GNUNET_new_array (
     199             :           result.details.ok.measures_length,
     200             :           struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails);
     201             :   }
     202           2 :   for (size_t i = 0; i < result.details.ok.measures_length; i++)
     203             :   {
     204           1 :     const json_t *measure_json = json_array_get (measures_array,
     205             :                                                  i);
     206             : 
     207           1 :     if (GNUNET_OK !=
     208           1 :         parse_measure_details (measure_json,
     209           1 :                                &measures[i]))
     210             :     {
     211           0 :       GNUNET_free (measures);
     212           0 :       return GNUNET_SYSERR;
     213             :     }
     214             :   }
     215           1 :   result.details.ok.measures = measures;
     216           1 :   algh->cb (algh->cb_cls,
     217             :             &result);
     218           1 :   algh->cb = NULL;
     219           1 :   GNUNET_free (measures);
     220           1 :   return GNUNET_OK;
     221             : }
     222             : 
     223             : 
     224             : /**
     225             :  * Function called when we're done processing the
     226             :  * HTTP /aml/$OFFICER_PUB/legitimizations GET request.
     227             :  *
     228             :  * @param cls the `struct TALER_EXCHANGE_AmlLegitimizationsGetHandle`
     229             :  * @param response_code HTTP response code, 0 on error
     230             :  * @param response parsed JSON result, NULL on error
     231             :  */
     232             : static void
     233           1 : handle_aml_legitimizations_get_finished (void *cls,
     234             :                                          long response_code,
     235             :                                          const void *response)
     236             : {
     237           1 :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh = cls;
     238           1 :   const json_t *j = response;
     239           1 :   struct TALER_EXCHANGE_AmlLegitimizationsGetResult result = {
     240             :     .hr.reply = j,
     241           1 :     .hr.http_status = (unsigned int) response_code
     242             :   };
     243             : 
     244           1 :   algh->job = NULL;
     245           1 :   switch (response_code)
     246             :   {
     247           0 :   case 0:
     248           0 :     result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     249           0 :     break;
     250           1 :   case MHD_HTTP_OK:
     251           1 :     if (GNUNET_OK !=
     252           1 :         handle_aml_legitimizations_get_ok (algh,
     253             :                                            j))
     254             :     {
     255           0 :       result.hr.http_status = 0;
     256           0 :       result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     257             :     }
     258           1 :     break;
     259           0 :   case MHD_HTTP_NO_CONTENT:
     260             :     /* can happen */
     261           0 :     break;
     262           0 :   case MHD_HTTP_BAD_REQUEST:
     263             :     /* This should never happen, either us or the exchange is buggy
     264             :        (or API version conflict); just pass JSON reply to the application */
     265           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     266           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     267           0 :     break;
     268           0 :   case MHD_HTTP_UNAUTHORIZED:
     269             :     /* Invalid officer credentials */
     270           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     271           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     272           0 :     break;
     273           0 :   case MHD_HTTP_FORBIDDEN:
     274             :     /* Officer not authorized for this operation */
     275           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     276           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     277           0 :     break;
     278           0 :   case MHD_HTTP_NOT_FOUND:
     279             :     /* Officer not found */
     280           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     281           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     282           0 :     break;
     283           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     284             :     /* Server had an internal issue; we should retry, but this API
     285             :        leaves this to the application */
     286           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     287           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     288           0 :     break;
     289           0 :   default:
     290             :     /* unexpected response code */
     291           0 :     GNUNET_break_op (0);
     292           0 :     result.hr.ec = TALER_JSON_get_error_code (j);
     293           0 :     result.hr.hint = TALER_JSON_get_error_hint (j);
     294           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     295             :                 "Unexpected response code %u/%d for GET %s\n",
     296             :                 (unsigned int) response_code,
     297             :                 (int) result.hr.ec,
     298             :                 algh->url);
     299           0 :     break;
     300             :   }
     301           1 :   if (NULL != algh->cb)
     302             :   {
     303           0 :     algh->cb (algh->cb_cls,
     304             :               &result);
     305           0 :     algh->cb = NULL;
     306             :   }
     307           1 :   TALER_EXCHANGE_aml_legitimizations_get_cancel (algh);
     308           1 : }
     309             : 
     310             : 
     311             : struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *
     312           1 : TALER_EXCHANGE_aml_legitimizations_get_create (
     313             :   struct GNUNET_CURL_Context *ctx,
     314             :   const char *exchange_base_url,
     315             :   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
     316             : {
     317             :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh;
     318             : 
     319           1 :   algh = GNUNET_new (struct TALER_EXCHANGE_AmlLegitimizationsGetHandle);
     320           1 :   algh->ctx = ctx;
     321           1 :   algh->exchange_base_url = GNUNET_strdup (exchange_base_url);
     322           1 :   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
     323             :                                       &algh->officer_pub.eddsa_pub);
     324           1 :   TALER_officer_aml_query_sign (officer_priv,
     325             :                                 &algh->officer_sig);
     326           1 :   algh->options.limit = -20; /* Default to last 20 entries */
     327           1 :   algh->options.offset = INT64_MAX; /* Default to maximum row id */
     328           1 :   algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */
     329           1 :   return algh;
     330             : }
     331             : 
     332             : 
     333             : enum GNUNET_GenericReturnValue
     334           1 : TALER_EXCHANGE_aml_legitimizations_get_set_options_ (
     335             :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
     336             :   unsigned int num_options,
     337             :   const struct TALER_EXCHANGE_AmlLegitimizationsGetOptionValue *options)
     338             : {
     339           3 :   for (unsigned int i = 0; i < num_options; i++)
     340             :   {
     341           3 :     switch (options[i].option)
     342             :     {
     343           1 :     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_END:
     344           1 :       return GNUNET_OK;
     345           0 :     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_LIMIT:
     346           0 :       algh->options.limit = options[i].details.limit;
     347           0 :       break;
     348           0 :     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_OFFSET:
     349           0 :       algh->options.offset = options[i].details.offset;
     350           0 :       break;
     351           1 :     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_H_PAYTO:
     352           1 :       algh->options.h_payto = options[i].details.h_payto;
     353           1 :       break;
     354           1 :     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_ACTIVE:
     355           1 :       algh->options.active = options[i].details.active;
     356           1 :       break;
     357           0 :     default:
     358           0 :       GNUNET_break (0);
     359           0 :       return GNUNET_NO;
     360             :     }
     361             :   }
     362           0 :   return GNUNET_OK;
     363             : }
     364             : 
     365             : 
     366             : enum TALER_EXCHANGE_AmlLegitimizationsGetStartError
     367           1 : TALER_EXCHANGE_aml_legitimizations_get_start (
     368             :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
     369             :   TALER_EXCHANGE_AmlLegitimizationsGetCallback cb,
     370             :   TALER_EXCHANGE__AML_LEGITIMIZATIONS_GET_RESULT_CLOSURE *cb_cls)
     371             : {
     372             :   char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
     373             :   char arg_str[sizeof (officer_pub_str) + 64];
     374             :   char limit_str[24];
     375             :   char offset_str[24];
     376             :   char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2];
     377             : 
     378           1 :   if (NULL != algh->job)
     379             :   {
     380           0 :     GNUNET_break (0);
     381           0 :     return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_AGAIN;
     382             :   }
     383           1 :   algh->cb = cb;
     384           1 :   algh->cb_cls = cb_cls;
     385           1 :   if (algh->options.offset > INT64_MAX)
     386             :   {
     387           0 :     GNUNET_break (0);
     388           0 :     algh->options.offset = INT64_MAX;
     389             :   }
     390             :   {
     391             :     char *end;
     392             : 
     393           1 :     end = GNUNET_STRINGS_data_to_string (
     394           1 :       &algh->officer_pub,
     395             :       sizeof (algh->officer_pub),
     396             :       officer_pub_str,
     397             :       sizeof (officer_pub_str));
     398           1 :     *end = '\0';
     399             :   }
     400           1 :   if (NULL != algh->options.h_payto)
     401             :   {
     402             :     char *end;
     403             : 
     404           1 :     end = GNUNET_STRINGS_data_to_string (
     405           1 :       algh->options.h_payto,
     406             :       sizeof (struct TALER_NormalizedPaytoHashP),
     407             :       paytoh_str,
     408             :       sizeof (paytoh_str));
     409           1 :     *end = '\0';
     410             :   }
     411             :   /* Build query parameters */
     412           1 :   GNUNET_snprintf (offset_str,
     413             :                    sizeof (offset_str),
     414             :                    "%llu",
     415           1 :                    (unsigned long long) algh->options.offset);
     416           1 :   GNUNET_snprintf (limit_str,
     417             :                    sizeof (limit_str),
     418             :                    "%lld",
     419           1 :                    (long long) algh->options.limit);
     420           1 :   GNUNET_snprintf (arg_str,
     421             :                    sizeof (arg_str),
     422             :                    "aml/%s/legitimizations",
     423             :                    officer_pub_str);
     424           2 :   algh->url = TALER_url_join (algh->exchange_base_url,
     425             :                               arg_str,
     426             :                               "limit",
     427             :                               limit_str,
     428             :                               "offset",
     429           1 :                               ( (algh->options.limit > 0) &&
     430           0 :                                 (0 == algh->options.offset) ) ||
     431           1 :                               ( (algh->options.limit <= 0) &&
     432           1 :                                 (INT64_MAX <= algh->options.offset) )
     433             :                               ? NULL
     434             :                               : offset_str,
     435             :                               "h_payto",
     436           1 :                               NULL == algh->options.h_payto
     437             :                               ? NULL
     438             :                               : paytoh_str,
     439             :                               "active",
     440           1 :                               TALER_EXCHANGE_YNA_ALL == algh->options.active
     441             :                               ? NULL
     442           1 :                               : TALER_yna_to_string (algh->options.active),
     443             :                               NULL);
     444           1 :   if (NULL == algh->url)
     445             :   {
     446           0 :     GNUNET_break (0);
     447           0 :     return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_INTERNAL;
     448             :   }
     449             : 
     450             :   {
     451             :     CURL *eh;
     452             : 
     453           1 :     eh = TALER_EXCHANGE_curl_easy_get_ (algh->url);
     454           1 :     if (NULL == eh)
     455             :     {
     456           0 :       GNUNET_break (0);
     457           0 :       GNUNET_free (algh->url);
     458           0 :       algh->url = NULL;
     459           0 :       return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_INTERNAL;
     460             :     }
     461             : 
     462             :     /* Add authentication header for AML officer */
     463             :     {
     464             :       char *hdr;
     465             :       char sig_str[sizeof (algh->officer_sig) * 2];
     466             :       char *end;
     467             : 
     468           1 :       end = GNUNET_STRINGS_data_to_string (
     469           1 :         &algh->officer_sig,
     470             :         sizeof (algh->officer_sig),
     471             :         sig_str,
     472             :         sizeof (sig_str));
     473           1 :       *end = '\0';
     474           1 :       GNUNET_asprintf (&hdr,
     475             :                        "%s: %s",
     476             :                        TALER_AML_OFFICER_SIGNATURE_HEADER,
     477             :                        sig_str);
     478           1 :       algh->job_headers = curl_slist_append (NULL,
     479             :                                              hdr);
     480           1 :       GNUNET_free (hdr);
     481             :     }
     482             :     algh->job
     483           2 :       = GNUNET_CURL_job_add2 (
     484             :           algh->ctx,
     485             :           eh,
     486           1 :           algh->job_headers,
     487             :           &handle_aml_legitimizations_get_finished,
     488             :           algh);
     489             :   }
     490           1 :   return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_OK;
     491             : }
     492             : 
     493             : 
     494             : void
     495           1 : TALER_EXCHANGE_aml_legitimizations_get_cancel (
     496             :   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh)
     497             : {
     498           1 :   if (NULL != algh->job)
     499             :   {
     500           0 :     GNUNET_CURL_job_cancel (algh->job);
     501           0 :     algh->job = NULL;
     502             :   }
     503           1 :   curl_slist_free_all (algh->job_headers);
     504           1 :   GNUNET_free (algh->exchange_base_url);
     505           1 :   GNUNET_free (algh->url);
     506           1 :   GNUNET_free (algh);
     507           1 : }
     508             : 
     509             : 
     510             : /* end of exchange_api_aml_legitimizations_get.c */

Generated by: LCOV version 1.16