exchange

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

taler-exchange-httpd_aml-decision.c (15081B)


      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 Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_aml-decision.c
     18  * @brief Handle POST request about an AML decision.
     19  * @author Christian Grothoff
     20  * @author Florian Dold
     21  */
     22 #include "taler/platform.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <jansson.h>
     26 #include <microhttpd.h>
     27 #include <pthread.h>
     28 #include "taler/taler_json_lib.h"
     29 #include "taler/taler_mhd_lib.h"
     30 #include "taler/taler_kyclogic_lib.h"
     31 #include "taler/taler_signatures.h"
     32 #include "taler-exchange-httpd_common_kyc.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 #include "taler-exchange-httpd_aml-decision.h"
     35 
     36 
     37 /**
     38  * Context used for processing the AML decision request.
     39  */
     40 struct AmlDecisionContext
     41 {
     42 
     43   /**
     44    * Kept in a DLL.
     45    */
     46   struct AmlDecisionContext *next;
     47 
     48   /**
     49    * Kept in a DLL.
     50    */
     51   struct AmlDecisionContext *prev;
     52 
     53   /**
     54    * HTTP status code to use with @e response.
     55    */
     56   unsigned int response_code;
     57 
     58   /**
     59    * Response to return, NULL if none yet.
     60    */
     61   struct MHD_Response *response;
     62 
     63   /**
     64    * Request we are processing.
     65    */
     66   struct TEH_RequestContext *rc;
     67 
     68   /**
     69    * Handle for async KYC processing.
     70    */
     71   struct TEH_KycMeasureRunContext *kat;
     72 
     73 };
     74 
     75 /**
     76  * Kept in a DLL.
     77  */
     78 static struct AmlDecisionContext *adc_head;
     79 
     80 /**
     81  * Kept in a DLL.
     82  */
     83 static struct AmlDecisionContext *adc_tail;
     84 
     85 
     86 void
     87 TEH_aml_decision_cleanup ()
     88 {
     89   struct AmlDecisionContext *adc;
     90 
     91   while (NULL != (adc = adc_head))
     92   {
     93     MHD_resume_connection (adc->rc->connection);
     94     GNUNET_CONTAINER_DLL_remove (adc_head,
     95                                  adc_tail,
     96                                  adc);
     97   }
     98 }
     99 
    100 
    101 /**
    102  * Function called to clean up aml decision context.
    103  *
    104  * @param[in,out] rc context to clean up
    105  */
    106 static void
    107 aml_decision_cleaner (struct TEH_RequestContext *rc)
    108 {
    109   struct AmlDecisionContext *adc = rc->rh_ctx;
    110 
    111   if (NULL != adc->kat)
    112   {
    113     TEH_kyc_run_measure_cancel (adc->kat);
    114     adc->kat = NULL;
    115   }
    116   if (NULL != adc->response)
    117   {
    118     MHD_destroy_response (adc->response);
    119     adc->response = NULL;
    120   }
    121   GNUNET_free (adc);
    122 }
    123 
    124 
    125 /**
    126  * Function called after the KYC-AML trigger is done.
    127  *
    128  * @param cls closure
    129  * @param ec error code or 0 on success
    130  * @param detail error message or NULL on success / no info
    131  */
    132 static void
    133 aml_trigger_callback (
    134   void *cls,
    135   enum TALER_ErrorCode ec,
    136   const char *detail)
    137 {
    138   struct AmlDecisionContext *adc = cls;
    139 
    140   adc->kat = NULL;
    141   GNUNET_assert (NULL == adc->response);
    142 
    143   if (TALER_EC_NONE != ec)
    144   {
    145     adc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    146     adc->response = TALER_MHD_make_error (
    147       ec,
    148       detail);
    149   }
    150   else
    151   {
    152     adc->response_code = MHD_HTTP_NO_CONTENT;
    153     adc->response = MHD_create_response_from_buffer_static (
    154       0,
    155       "");
    156     TALER_MHD_add_global_headers (adc->response,
    157                                   true);
    158   }
    159 
    160   GNUNET_assert (NULL != adc->response);
    161 
    162   MHD_resume_connection (adc->rc->connection);
    163   GNUNET_CONTAINER_DLL_remove (adc_head,
    164                                adc_tail,
    165                                adc);
    166   TALER_MHD_daemon_trigger ();
    167 }
    168 
    169 
    170 MHD_RESULT
    171 TEH_handler_post_aml_decision (
    172   struct TEH_RequestContext *rc,
    173   const struct TALER_AmlOfficerPublicKeyP *officer_pub,
    174   const json_t *root)
    175 {
    176   struct MHD_Connection *connection = rc->connection;
    177   struct AmlDecisionContext *adc = rc->rh_ctx;
    178   const char *justification;
    179   const char *new_measures = NULL;
    180   bool to_investigate;
    181   struct GNUNET_TIME_Timestamp decision_time;
    182   struct GNUNET_TIME_Timestamp attributes_expiration
    183     = GNUNET_TIME_UNIT_ZERO_TS;
    184   const json_t *new_rules;
    185   const json_t *events = NULL;
    186   const json_t *properties = NULL;
    187   const json_t *attributes = NULL;
    188   struct TALER_FullPayto payto_uri = {
    189     .full_payto = NULL
    190   };
    191   struct TALER_NormalizedPaytoHashP h_payto;
    192   struct TALER_AmlOfficerSignatureP officer_sig;
    193   struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
    194   uint64_t legi_measure_serial_id = 0;
    195   MHD_RESULT ret;
    196   struct GNUNET_JSON_Specification spec[] = {
    197     GNUNET_JSON_spec_mark_optional (
    198       GNUNET_JSON_spec_string (
    199         "new_measures",
    200         &new_measures),
    201       NULL),
    202     GNUNET_JSON_spec_string ("justification",
    203                              &justification),
    204     GNUNET_JSON_spec_mark_optional (
    205       TALER_JSON_spec_full_payto_uri ("payto_uri",
    206                                       &payto_uri),
    207       NULL),
    208     GNUNET_JSON_spec_fixed_auto ("h_payto",
    209                                  &h_payto),
    210     GNUNET_JSON_spec_object_const ("new_rules",
    211                                    &new_rules),
    212     GNUNET_JSON_spec_mark_optional (
    213       GNUNET_JSON_spec_object_const ("properties",
    214                                      &properties),
    215       NULL),
    216     GNUNET_JSON_spec_mark_optional (
    217       GNUNET_JSON_spec_array_const ("events",
    218                                     &events),
    219       NULL),
    220     GNUNET_JSON_spec_bool ("keep_investigating",
    221                            &to_investigate),
    222     GNUNET_JSON_spec_fixed_auto ("officer_sig",
    223                                  &officer_sig),
    224     GNUNET_JSON_spec_timestamp ("decision_time",
    225                                 &decision_time),
    226     GNUNET_JSON_spec_mark_optional (
    227       GNUNET_JSON_spec_timestamp ("attributes_expiration",
    228                                   &attributes_expiration),
    229       NULL),
    230     GNUNET_JSON_spec_mark_optional (
    231       GNUNET_JSON_spec_object_const ("attributes",
    232                                      &attributes),
    233       NULL),
    234     GNUNET_JSON_spec_end ()
    235   };
    236   struct GNUNET_TIME_Timestamp expiration_time;
    237   json_t *jmeasures = NULL;
    238   bool is_wallet;
    239   size_t eas = 0;
    240   void *ea = NULL;
    241 
    242   if (NULL == adc)
    243   {
    244     /* Initialize context */
    245     adc = GNUNET_new (struct AmlDecisionContext);
    246     adc->rc = rc;
    247     rc->rh_ctx = adc;
    248     rc->rh_cleaner = aml_decision_cleaner;
    249   }
    250 
    251   if (NULL != adc->response)
    252   {
    253     ret = MHD_queue_response (rc->connection,
    254                               adc->response_code,
    255                               adc->response);
    256     goto done;
    257   }
    258 
    259   {
    260     enum GNUNET_GenericReturnValue res;
    261 
    262     res = TALER_MHD_parse_json_data (connection,
    263                                      root,
    264                                      spec);
    265     if (GNUNET_SYSERR == res)
    266     {
    267       ret = MHD_NO; /* hard failure */
    268       goto done;
    269     }
    270     if (GNUNET_NO == res)
    271     {
    272       GNUNET_break_op (0);
    273       ret = MHD_YES /* failure */;
    274       goto done;
    275     }
    276     if ( (NULL != attributes) &&
    277          (! json_is_string (json_object_get (attributes,
    278                                              "FORM_ID"))) )
    279     {
    280       GNUNET_break_op (0);
    281       ret = TALER_MHD_reply_with_error (
    282         connection,
    283         MHD_HTTP_BAD_REQUEST,
    284         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    285         "attributes['FORM_ID']");
    286       goto done;
    287     }
    288   }
    289   if (NULL != payto_uri.full_payto)
    290   {
    291     struct TALER_NormalizedPaytoHashP h_payto2;
    292 
    293     TALER_full_payto_normalize_and_hash (payto_uri,
    294                                          &h_payto2);
    295     if (0 !=
    296         GNUNET_memcmp (&h_payto,
    297                        &h_payto2))
    298     {
    299       GNUNET_break_op (0);
    300       ret = TALER_MHD_reply_with_error (
    301         connection,
    302         MHD_HTTP_BAD_REQUEST,
    303         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    304         "payto_uri");
    305       goto done;
    306     }
    307   }
    308 
    309   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    310   if (GNUNET_OK !=
    311       TALER_officer_aml_decision_verify (
    312         justification,
    313         decision_time,
    314         &h_payto,
    315         new_rules,
    316         properties,
    317         new_measures,
    318         to_investigate,
    319         officer_pub,
    320         &officer_sig,
    321         attributes_expiration,
    322         attributes))
    323   {
    324     GNUNET_break_op (0);
    325     ret = TALER_MHD_reply_with_error (
    326       connection,
    327       MHD_HTTP_FORBIDDEN,
    328       TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
    329       NULL);
    330     goto done;
    331   }
    332 
    333   lrs = TALER_KYCLOGIC_rules_parse (new_rules);
    334   if (NULL == lrs)
    335   {
    336     GNUNET_break_op (0);
    337     ret = TALER_MHD_reply_with_error (
    338       connection,
    339       MHD_HTTP_BAD_REQUEST,
    340       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    341       "legitimization rule malformed");
    342     goto done;
    343   }
    344 
    345   expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
    346   if ( (NULL != new_measures) &&
    347        (0 != strlen (new_measures)) )
    348   {
    349     jmeasures
    350       = TALER_KYCLOGIC_get_jmeasures (lrs,
    351                                       new_measures);
    352     if (NULL == jmeasures)
    353     {
    354       GNUNET_break_op (0);
    355       /* Request specified a new_measure for which the given
    356          rule set does not work as it does not define the measure */
    357       ret = TALER_MHD_reply_with_error (
    358         connection,
    359         MHD_HTTP_BAD_REQUEST,
    360         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    361         "new_measures/new_rules");
    362       goto done;
    363     }
    364   }
    365 
    366   {
    367     size_t num_events = json_array_size (events);
    368     const char *sevents[GNUNET_NZL (num_events)];
    369     enum GNUNET_DB_QueryStatus qs;
    370     struct GNUNET_TIME_Timestamp last_date;
    371     bool invalid_officer = true;
    372     bool unknown_account = false;
    373     struct GNUNET_HashCode h_attr = { 0 };
    374     const char *form_id = NULL;
    375 
    376     if (NULL != attributes)
    377     {
    378       form_id = json_string_value (json_object_get (attributes,
    379                                                     "FORM_ID"));
    380       TALER_json_hash (attributes,
    381                        &h_attr);
    382       TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
    383                                            attributes,
    384                                            &ea,
    385                                            &eas);
    386     }
    387 
    388     for (size_t i = 0; i<num_events; i++)
    389     {
    390       sevents[i] = json_string_value (json_array_get (events,
    391                                                       i));
    392       if (NULL == sevents[i])
    393       {
    394         GNUNET_break_op (0);
    395         ret = TALER_MHD_reply_with_error (
    396           connection,
    397           MHD_HTTP_BAD_REQUEST,
    398           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    399           "events");
    400         goto done;
    401       }
    402     }
    403     /* We keep 'new_measures' around mostly so that
    404        the auditor can later verify officer_sig */
    405     qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
    406                                           payto_uri,
    407                                           &h_payto,
    408                                           decision_time,
    409                                           expiration_time,
    410                                           properties,
    411                                           new_rules,
    412                                           to_investigate,
    413                                           new_measures,
    414                                           jmeasures,
    415                                           justification,
    416                                           officer_pub,
    417                                           &officer_sig,
    418                                           num_events,
    419                                           sevents,
    420                                           form_id,
    421                                           eas, /* enc_attributes_size*/
    422                                           ea, /* enc_attributes*/
    423                                           NULL != attributes
    424                                           ? &h_attr
    425                                           : NULL, /* attributes_hash */
    426                                           attributes_expiration,
    427                                           &invalid_officer,
    428                                           &unknown_account,
    429                                           &last_date,
    430                                           &legi_measure_serial_id,
    431                                           &is_wallet);
    432     json_decref (jmeasures);
    433     if (qs <= 0)
    434     {
    435       GNUNET_break (0);
    436       ret = TALER_MHD_reply_with_error (
    437         connection,
    438         MHD_HTTP_INTERNAL_SERVER_ERROR,
    439         TALER_EC_GENERIC_DB_STORE_FAILED,
    440         "insert_aml_decision");
    441       goto done;
    442     }
    443     if (invalid_officer)
    444     {
    445       GNUNET_break_op (0);
    446       ret = TALER_MHD_reply_with_error (
    447         connection,
    448         MHD_HTTP_FORBIDDEN,
    449         TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
    450         NULL);
    451       goto done;
    452     }
    453     if (unknown_account)
    454     {
    455       GNUNET_break_op (0);
    456       ret = TALER_MHD_reply_with_error (
    457         connection,
    458         MHD_HTTP_NOT_FOUND,
    459         TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
    460         "h_payto");
    461       goto done;
    462     }
    463     if (GNUNET_TIME_timestamp_cmp (last_date,
    464                                    >,
    465                                    decision_time))
    466     {
    467       GNUNET_break_op (0);
    468       ret = TALER_MHD_reply_with_error (
    469         connection,
    470         MHD_HTTP_CONFLICT,
    471         TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
    472         NULL);
    473       goto done;
    474     }
    475   }
    476   /* Run instant measure if necessary */
    477   {
    478     const struct TALER_KYCLOGIC_Measure *instant_ms = NULL;
    479 
    480     if (NULL != new_measures)
    481     {
    482       instant_ms = TALER_KYCLOGIC_get_instant_measure (lrs,
    483                                                        new_measures);
    484     }
    485 
    486     if (NULL != instant_ms)
    487     {
    488       /* We have an 'instant' measure which means we must run the
    489          AML program immediately instead of waiting for the account owner
    490          to select some measure and contribute their KYC data. */
    491 
    492       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    493                   "Running instant measure after AML decision\n");
    494       adc->kat = TEH_kyc_run_measure_directly (
    495         &rc->async_scope_id,
    496         instant_ms,
    497         &h_payto,
    498         is_wallet,
    499         &aml_trigger_callback,
    500         adc
    501         );
    502       if (NULL == adc->kat)
    503       {
    504         GNUNET_break (0);
    505         ret = TALER_MHD_reply_with_error (
    506           rc->connection,
    507           MHD_HTTP_INTERNAL_SERVER_ERROR,
    508           TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
    509           "TEH_kyc_finished");
    510         goto done;
    511       }
    512       MHD_suspend_connection (adc->rc->connection);
    513       GNUNET_CONTAINER_DLL_insert (adc_head,
    514                                    adc_tail,
    515                                    adc);
    516       ret = MHD_YES;
    517       goto done;
    518     }
    519   }
    520   ret = TALER_MHD_reply_static (
    521     connection,
    522     MHD_HTTP_NO_CONTENT,
    523     NULL,
    524     NULL,
    525     0);
    526   goto done;
    527 
    528 done:
    529   TALER_KYCLOGIC_rules_free (lrs);
    530   GNUNET_free (ea);
    531   return ret;
    532 }
    533 
    534 
    535 /* end of taler-exchange-httpd_aml-decision.c */