LCOV - code coverage report
Current view: top level - lib - exchange_api_kyc_check.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 87 141 61.7 %
Date: 2025-06-05 21:03:14 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2021-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_kyc_check.c
      19             :  * @brief Implementation of the /kyc-check request
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <microhttpd.h> /* just for HTTP check codes */
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <gnunet/gnunet_curl_lib.h>
      26             : #include "taler_exchange_service.h"
      27             : #include "taler_json_lib.h"
      28             : #include "exchange_api_handle.h"
      29             : #include "taler_signatures.h"
      30             : #include "exchange_api_curl_defaults.h"
      31             : 
      32             : 
      33             : /**
      34             :  * @brief A ``/kyc-check`` handle
      35             :  */
      36             : struct TALER_EXCHANGE_KycCheckHandle
      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_KycStatusCallback cb;
      53             : 
      54             :   /**
      55             :    * Closure for @e cb.
      56             :    */
      57             :   void *cb_cls;
      58             : 
      59             : };
      60             : 
      61             : 
      62             : static enum GNUNET_GenericReturnValue
      63          14 : parse_account_status (
      64             :   struct TALER_EXCHANGE_KycCheckHandle *kch,
      65             :   const json_t *j,
      66             :   struct TALER_EXCHANGE_KycStatus *ks,
      67             :   struct TALER_EXCHANGE_AccountKycStatus *status)
      68             : {
      69          14 :   const json_t *limits = NULL;
      70             :   struct GNUNET_JSON_Specification spec[] = {
      71          14 :     GNUNET_JSON_spec_bool ("aml_review",
      72             :                            &status->aml_review),
      73          14 :     GNUNET_JSON_spec_uint64 ("rule_gen",
      74             :                              &status->rule_gen),
      75          14 :     GNUNET_JSON_spec_fixed_auto ("access_token",
      76             :                                  &status->access_token),
      77          14 :     GNUNET_JSON_spec_mark_optional (
      78             :       GNUNET_JSON_spec_array_const ("limits",
      79             :                                     &limits),
      80             :       NULL),
      81          14 :     GNUNET_JSON_spec_end ()
      82             :   };
      83             : 
      84          14 :   if (GNUNET_OK !=
      85          14 :       GNUNET_JSON_parse (j,
      86             :                          spec,
      87             :                          NULL, NULL))
      88             :   {
      89           0 :     GNUNET_break_op (0);
      90           0 :     return GNUNET_SYSERR;
      91             :   }
      92          28 :   if ( (NULL != limits) &&
      93          14 :        (0 != json_array_size (limits)) )
      94          13 :   {
      95          13 :     size_t limit_length = json_array_size (limits);
      96          13 :     struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
      97             :     size_t i;
      98             :     json_t *limit;
      99             : 
     100          47 :     json_array_foreach (limits, i, limit)
     101             :     {
     102          34 :       struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
     103             :       struct GNUNET_JSON_Specification ispec[] = {
     104          34 :         GNUNET_JSON_spec_mark_optional (
     105             :           GNUNET_JSON_spec_bool ("soft_limit",
     106             :                                  &al->soft_limit),
     107             :           NULL),
     108          34 :         GNUNET_JSON_spec_relative_time ("timeframe",
     109             :                                         &al->timeframe),
     110          34 :         TALER_JSON_spec_kycte ("operation_type",
     111             :                                &al->operation_type),
     112          34 :         TALER_JSON_spec_amount_any ("threshold",
     113             :                                     &al->threshold),
     114          34 :         GNUNET_JSON_spec_end ()
     115             :       };
     116             : 
     117          34 :       al->soft_limit = false;
     118          34 :       if (GNUNET_OK !=
     119          34 :           GNUNET_JSON_parse (limit,
     120             :                              ispec,
     121             :                              NULL, NULL))
     122             :       {
     123           0 :         GNUNET_break_op (0);
     124           0 :         return GNUNET_SYSERR;
     125             :       }
     126             :     }
     127          13 :     status->limits = ala;
     128          13 :     status->limits_length = limit_length;
     129          13 :     kch->cb (kch->cb_cls,
     130             :              ks);
     131             :   }
     132             :   else
     133             :   {
     134           1 :     kch->cb (kch->cb_cls,
     135             :              ks);
     136             :   }
     137          14 :   GNUNET_JSON_parse_free (spec);
     138          14 :   return GNUNET_OK;
     139             : }
     140             : 
     141             : 
     142             : /**
     143             :  * Function called when we're done processing the
     144             :  * HTTP /kyc-check request.
     145             :  *
     146             :  * @param cls the `struct TALER_EXCHANGE_KycCheckHandle`
     147             :  * @param response_code HTTP response code, 0 on error
     148             :  * @param response parsed JSON result, NULL on error
     149             :  */
     150             : static void
     151          16 : handle_kyc_check_finished (void *cls,
     152             :                            long response_code,
     153             :                            const void *response)
     154             : {
     155          16 :   struct TALER_EXCHANGE_KycCheckHandle *kch = cls;
     156          16 :   const json_t *j = response;
     157          16 :   struct TALER_EXCHANGE_KycStatus ks = {
     158             :     .hr.reply = j,
     159          16 :     .hr.http_status = (unsigned int) response_code
     160             :   };
     161             : 
     162          16 :   kch->job = NULL;
     163          16 :   switch (response_code)
     164             :   {
     165           0 :   case 0:
     166           0 :     ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     167           0 :     break;
     168           3 :   case MHD_HTTP_OK:
     169             :     {
     170           3 :       if (GNUNET_OK !=
     171           3 :           parse_account_status (kch,
     172             :                                 j,
     173             :                                 &ks,
     174             :                                 &ks.details.ok))
     175             :       {
     176           0 :         GNUNET_break_op (0);
     177           0 :         ks.hr.http_status = 0;
     178           0 :         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     179           0 :         break;
     180             :       }
     181           3 :       TALER_EXCHANGE_kyc_check_cancel (kch);
     182          14 :       return;
     183             :     }
     184          11 :   case MHD_HTTP_ACCEPTED:
     185             :     {
     186          11 :       if (GNUNET_OK !=
     187          11 :           parse_account_status (kch,
     188             :                                 j,
     189             :                                 &ks,
     190             :                                 &ks.details.accepted))
     191             :       {
     192           0 :         GNUNET_break_op (0);
     193           0 :         ks.hr.http_status = 0;
     194           0 :         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     195           0 :         break;
     196             :       }
     197          11 :       TALER_EXCHANGE_kyc_check_cancel (kch);
     198          11 :       return;
     199             :     }
     200           2 :   case MHD_HTTP_NO_CONTENT:
     201           2 :     break;
     202           0 :   case MHD_HTTP_BAD_REQUEST:
     203           0 :     ks.hr.ec = TALER_JSON_get_error_code (j);
     204             :     /* This should never happen, either us or the exchange is buggy
     205             :        (or API version conflict); just pass JSON reply to the application */
     206           0 :     break;
     207           0 :   case MHD_HTTP_FORBIDDEN:
     208             :     {
     209             :       struct GNUNET_JSON_Specification spec[] = {
     210           0 :         GNUNET_JSON_spec_fixed_auto (
     211             :           "expected_account_pub",
     212             :           &ks.details.forbidden.expected_account_pub),
     213           0 :         TALER_JSON_spec_ec ("code",
     214             :                             &ks.hr.ec),
     215           0 :         GNUNET_JSON_spec_end ()
     216             :       };
     217             : 
     218           0 :       if (GNUNET_OK !=
     219           0 :           GNUNET_JSON_parse (j,
     220             :                              spec,
     221             :                              NULL, NULL))
     222             :       {
     223           0 :         GNUNET_break_op (0);
     224           0 :         ks.hr.http_status = 0;
     225           0 :         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     226           0 :         break;
     227             :       }
     228           0 :       break;
     229             :     }
     230           0 :   case MHD_HTTP_NOT_FOUND:
     231           0 :     ks.hr.ec = TALER_JSON_get_error_code (j);
     232           0 :     break;
     233           0 :   case MHD_HTTP_CONFLICT:
     234           0 :     ks.hr.ec = TALER_JSON_get_error_code (j);
     235           0 :     break;
     236           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     237           0 :     ks.hr.ec = TALER_JSON_get_error_code (j);
     238             :     /* Server had an internal issue; we should retry, but this API
     239             :        leaves this to the application */
     240           0 :     break;
     241           0 :   default:
     242             :     /* unexpected response code */
     243           0 :     GNUNET_break_op (0);
     244           0 :     ks.hr.ec = TALER_JSON_get_error_code (j);
     245           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     246             :                 "Unexpected response code %u/%d for exchange kyc_check\n",
     247             :                 (unsigned int) response_code,
     248             :                 (int) ks.hr.ec);
     249           0 :     break;
     250             :   }
     251           2 :   kch->cb (kch->cb_cls,
     252             :            &ks);
     253           2 :   TALER_EXCHANGE_kyc_check_cancel (kch);
     254             : }
     255             : 
     256             : 
     257             : struct TALER_EXCHANGE_KycCheckHandle *
     258          16 : TALER_EXCHANGE_kyc_check (
     259             :   struct GNUNET_CURL_Context *ctx,
     260             :   const char *url,
     261             :   const struct TALER_NormalizedPaytoHashP *h_payto,
     262             :   const union TALER_AccountPrivateKeyP *account_priv,
     263             :   uint64_t known_rule_gen,
     264             :   enum TALER_EXCHANGE_KycLongPollTarget lpt,
     265             :   struct GNUNET_TIME_Relative timeout,
     266             :   TALER_EXCHANGE_KycStatusCallback cb,
     267             :   void *cb_cls)
     268             : {
     269             :   struct TALER_EXCHANGE_KycCheckHandle *kch;
     270             :   CURL *eh;
     271             :   char arg_str[128];
     272             :   char timeout_ms[32];
     273             :   char lpt_str[32];
     274             :   char krg_str[32];
     275          16 :   struct curl_slist *job_headers = NULL;
     276             :   unsigned long long tms;
     277             : 
     278             :   {
     279             :     char *hps;
     280             : 
     281          16 :     hps = GNUNET_STRINGS_data_to_string_alloc (
     282             :       h_payto,
     283             :       sizeof (*h_payto));
     284          16 :     GNUNET_snprintf (arg_str,
     285             :                      sizeof (arg_str),
     286             :                      "kyc-check/%s",
     287             :                      hps);
     288          16 :     GNUNET_free (hps);
     289             :   }
     290          32 :   tms = timeout.rel_value_us
     291          16 :         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
     292          16 :   GNUNET_snprintf (timeout_ms,
     293             :                    sizeof (timeout_ms),
     294             :                    "%llu",
     295             :                    tms);
     296          16 :   GNUNET_snprintf (krg_str,
     297             :                    sizeof (krg_str),
     298             :                    "%llu",
     299             :                    (unsigned long long) known_rule_gen);
     300          16 :   GNUNET_snprintf (lpt_str,
     301             :                    sizeof (lpt_str),
     302             :                    "%d",
     303             :                    (int) lpt);
     304          16 :   kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
     305          16 :   kch->cb = cb;
     306          16 :   kch->cb_cls = cb_cls;
     307             :   kch->url
     308          32 :     = TALER_url_join (
     309             :         url,
     310             :         arg_str,
     311             :         "timeout_ms",
     312          16 :         GNUNET_TIME_relative_is_zero (timeout)
     313             :         ? NULL
     314             :         : timeout_ms,
     315             :         "min_rule",
     316             :         0 == known_rule_gen
     317             :         ? NULL
     318             :         : krg_str,
     319             :         "lpt",
     320             :         TALER_EXCHANGE_KLPT_NONE == lpt
     321             :         ? NULL
     322             :         : lpt_str,
     323             :         NULL);
     324          16 :   if (NULL == kch->url)
     325             :   {
     326           0 :     GNUNET_free (kch);
     327           0 :     return NULL;
     328             :   }
     329          16 :   eh = TALER_EXCHANGE_curl_easy_get_ (kch->url);
     330          16 :   if (NULL == eh)
     331             :   {
     332           0 :     GNUNET_break (0);
     333           0 :     GNUNET_free (kch->url);
     334           0 :     GNUNET_free (kch);
     335           0 :     return NULL;
     336             :   }
     337          16 :   if (0 != tms)
     338             :   {
     339          14 :     GNUNET_break (CURLE_OK ==
     340             :                   curl_easy_setopt (eh,
     341             :                                     CURLOPT_TIMEOUT_MS,
     342             :                                     (long) (tms + 500L)));
     343             :   }
     344             :   job_headers
     345          16 :     = curl_slist_append (
     346             :         job_headers,
     347             :         "Content-Type: application/json");
     348             :   {
     349             :     union TALER_AccountSignatureP account_sig;
     350             :     char *sig_hdr;
     351             :     char *hdr;
     352             : 
     353          16 :     TALER_account_kyc_auth_sign (account_priv,
     354             :                                  &account_sig);
     355             : 
     356          16 :     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
     357             :       &account_sig,
     358             :       sizeof (account_sig));
     359          16 :     GNUNET_asprintf (&hdr,
     360             :                      "%s: %s",
     361             :                      TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
     362             :                      sig_hdr);
     363          16 :     GNUNET_free (sig_hdr);
     364          16 :     job_headers = curl_slist_append (job_headers,
     365             :                                      hdr);
     366          16 :     GNUNET_free (hdr);
     367          16 :     if (NULL == job_headers)
     368             :     {
     369           0 :       GNUNET_break (0);
     370           0 :       curl_easy_cleanup (eh);
     371           0 :       return NULL;
     372             :     }
     373             :   }
     374             :   kch->job
     375          16 :     = GNUNET_CURL_job_add2 (ctx,
     376             :                             eh,
     377             :                             job_headers,
     378             :                             &handle_kyc_check_finished,
     379             :                             kch);
     380          16 :   curl_slist_free_all (job_headers);
     381          16 :   return kch;
     382             : }
     383             : 
     384             : 
     385             : void
     386          16 : TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
     387             : {
     388          16 :   if (NULL != kch->job)
     389             :   {
     390           0 :     GNUNET_CURL_job_cancel (kch->job);
     391           0 :     kch->job = NULL;
     392             :   }
     393          16 :   GNUNET_free (kch->url);
     394          16 :   GNUNET_free (kch);
     395          16 : }
     396             : 
     397             : 
     398             : /* end of exchange_api_kyc_check.c */

Generated by: LCOV version 1.16