merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-private-accounts.c (14492B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-private-accounts.c
     22  * @brief implementing POST /private/accounts request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_post-private-accounts.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include "taler/taler_merchant_bank_lib.h"
     29 #include <taler/taler_dbevents.h>
     30 #include <taler/taler_json_lib.h>
     31 #include "taler-merchant-httpd_mfa.h"
     32 #include <regex.h>
     33 #include "merchant-database/activate_account.h"
     34 #include "merchant-database/select_accounts_by_instance.h"
     35 #include "merchant-database/preflight.h"
     36 
     37 /**
     38  * Maximum number of retries we do on serialization failures.
     39  */
     40 #define MAX_RETRIES 5
     41 
     42 /**
     43  * Closure for account_cb().
     44  */
     45 struct PostAccountContext
     46 {
     47   /**
     48    * Payto URI of the account to add (from the request).
     49    */
     50   struct TALER_FullPayto uri;
     51 
     52   /**
     53    * Hash of the wire details (@e uri and @e salt).
     54    * Set if @e have_same_account is true.
     55    */
     56   struct TALER_MerchantWireHashP h_wire;
     57 
     58   /**
     59    * Salt value used for hashing @e uri.
     60    * Set if @e have_same_account is true.
     61    */
     62   struct TALER_WireSaltP salt;
     63 
     64   /**
     65    * Credit facade URL from the request.
     66    */
     67   const char *credit_facade_url;
     68 
     69   /**
     70    * Facade credentials from the request.
     71    */
     72   const json_t *credit_facade_credentials;
     73 
     74   /**
     75    * Wire subject metadata from the request.
     76    */
     77   const char *extra_wire_subject_metadata;
     78 
     79   /**
     80    * True if we have ANY account already and thus require MFA.
     81    */
     82   bool have_any_account;
     83 
     84   /**
     85    * True if we have exact match already and thus require MFA.
     86    */
     87   bool have_same_account;
     88 
     89   /**
     90    * True if we have an account with the same normalized payto
     91    * already and thus the client can only do PATCH but not POST.
     92    */
     93   bool have_conflicting_account;
     94 };
     95 
     96 
     97 /**
     98  * Callback invoked with information about a bank account.
     99  *
    100  * @param cls closure with a `struct PostAccountContext`
    101  * @param merchant_priv private key of the merchant instance
    102  * @param ad details about the account
    103  */
    104 static void
    105 account_cb (
    106   void *cls,
    107   const struct TALER_MerchantPrivateKeyP *merchant_priv,
    108   const struct TALER_MERCHANTDB_AccountDetails *ad)
    109 {
    110   struct PostAccountContext *pac = cls;
    111 
    112   if (! ad->active)
    113     return;
    114   pac->have_any_account = true;
    115   if ( (0 == TALER_full_payto_cmp (pac->uri,
    116                                    ad->payto_uri) ) &&
    117        ( (pac->credit_facade_credentials ==
    118           ad->credit_facade_credentials) ||
    119          ( (NULL != pac->credit_facade_credentials) &&
    120            (NULL != ad->credit_facade_credentials) &&
    121            (1 == json_equal (pac->credit_facade_credentials,
    122                              ad->credit_facade_credentials)) ) ) &&
    123        ( (pac->extra_wire_subject_metadata ==
    124           ad->extra_wire_subject_metadata) ||
    125          ( (NULL != pac->extra_wire_subject_metadata) &&
    126            (NULL != ad->extra_wire_subject_metadata) &&
    127            (0 == strcmp (pac->extra_wire_subject_metadata,
    128                          ad->extra_wire_subject_metadata)) ) ) &&
    129        ( (pac->credit_facade_url == ad->credit_facade_url) ||
    130          ( (NULL != pac->credit_facade_url) &&
    131            (NULL != ad->credit_facade_url) &&
    132            (0 == strcmp (pac->credit_facade_url,
    133                          ad->credit_facade_url)) ) ) )
    134   {
    135     pac->have_same_account = true;
    136     pac->salt = ad->salt;
    137     pac->h_wire = ad->h_wire;
    138     return;
    139   }
    140 
    141   if (0 == TALER_full_payto_normalize_and_cmp (pac->uri,
    142                                                ad->payto_uri) )
    143   {
    144     pac->have_conflicting_account = true;
    145     return;
    146   }
    147 }
    148 
    149 
    150 enum MHD_Result
    151 TMH_private_post_account (const struct TMH_RequestHandler *rh,
    152                           struct MHD_Connection *connection,
    153                           struct TMH_HandlerContext *hc)
    154 {
    155   struct TMH_MerchantInstance *mi = hc->instance;
    156   struct PostAccountContext pac = { 0 };
    157   struct GNUNET_JSON_Specification ispec[] = {
    158     TALER_JSON_spec_full_payto_uri ("payto_uri",
    159                                     &pac.uri),
    160     GNUNET_JSON_spec_mark_optional (
    161       TALER_JSON_spec_web_url ("credit_facade_url",
    162                                &pac.credit_facade_url),
    163       NULL),
    164     GNUNET_JSON_spec_mark_optional (
    165       GNUNET_JSON_spec_string ("extra_wire_subject_metadata",
    166                                &pac.extra_wire_subject_metadata),
    167       NULL),
    168     GNUNET_JSON_spec_mark_optional (
    169       GNUNET_JSON_spec_object_const ("credit_facade_credentials",
    170                                      &pac.credit_facade_credentials),
    171       NULL),
    172     GNUNET_JSON_spec_end ()
    173   };
    174 
    175   {
    176     enum GNUNET_GenericReturnValue res;
    177 
    178     res = TALER_MHD_parse_json_data (connection,
    179                                      hc->request_body,
    180                                      ispec);
    181     if (GNUNET_OK != res)
    182       return (GNUNET_NO == res)
    183              ? MHD_YES
    184              : MHD_NO;
    185   }
    186 
    187   {
    188     char *err;
    189 
    190     if (NULL !=
    191         (err = TALER_payto_validate (pac.uri)))
    192     {
    193       enum MHD_Result mret;
    194 
    195       GNUNET_break_op (0);
    196       mret = TALER_MHD_reply_with_error (
    197         connection,
    198         MHD_HTTP_BAD_REQUEST,
    199         TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
    200         err);
    201       GNUNET_free (err);
    202       return mret;
    203     }
    204   }
    205   if (! TALER_is_valid_subject_metadata_string (
    206         pac.extra_wire_subject_metadata))
    207   {
    208     GNUNET_break_op (0);
    209     return TALER_MHD_reply_with_error (
    210       connection,
    211       MHD_HTTP_BAD_REQUEST,
    212       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    213       "extra_wire_subject_metadata");
    214   }
    215 
    216   {
    217     char *apt = GNUNET_strdup (TMH_allowed_payment_targets);
    218     char *method = TALER_payto_get_method (pac.uri.full_payto);
    219     bool ok;
    220 
    221     ok = false;
    222     for (const char *tok = strtok (apt,
    223                                    " ");
    224          NULL != tok;
    225          tok = strtok (NULL,
    226                        " "))
    227     {
    228       if (0 == strcmp ("*",
    229                        tok))
    230         ok = true;
    231       if (0 == strcmp (method,
    232                        tok))
    233         ok = true;
    234       if (ok)
    235         break;
    236     }
    237     GNUNET_free (method);
    238     GNUNET_free (apt);
    239     if (! ok)
    240     {
    241       GNUNET_break_op (0);
    242       return TALER_MHD_reply_with_error (
    243         connection,
    244         MHD_HTTP_BAD_REQUEST,
    245         TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
    246         "The payment target type is forbidden by policy");
    247     }
    248   }
    249 
    250   if ( (NULL != TMH_payment_target_regex) &&
    251        (0 !=
    252         regexec (&TMH_payment_target_re,
    253                  pac.uri.full_payto,
    254                  0,
    255                  NULL,
    256                  0)) )
    257   {
    258     GNUNET_break_op (0);
    259     return TALER_MHD_reply_with_error (
    260       connection,
    261       MHD_HTTP_BAD_REQUEST,
    262       TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
    263       "The specific account is forbidden by policy");
    264   }
    265 
    266   if ( (NULL == pac.credit_facade_url) !=
    267        (NULL == pac.credit_facade_credentials) )
    268   {
    269     GNUNET_break_op (0);
    270     return TALER_MHD_reply_with_error (
    271       connection,
    272       MHD_HTTP_BAD_REQUEST,
    273       TALER_EC_GENERIC_PARAMETER_MISSING,
    274       (NULL == pac.credit_facade_url)
    275                                        ? "credit_facade_url"
    276                                        : "credit_facade_credentials");
    277   }
    278   if ( (NULL != pac.credit_facade_url) ||
    279        (NULL != pac.credit_facade_credentials) )
    280   {
    281     struct TALER_MERCHANT_BANK_AuthenticationData auth;
    282 
    283     if (GNUNET_OK !=
    284         TALER_MERCHANT_BANK_auth_parse_json (pac.credit_facade_credentials,
    285                                              pac.credit_facade_url,
    286                                              &auth))
    287     {
    288       GNUNET_break_op (0);
    289       return TALER_MHD_reply_with_error (
    290         connection,
    291         MHD_HTTP_BAD_REQUEST,
    292         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    293         "credit_facade_url or credit_facade_credentials");
    294     }
    295     TALER_MERCHANT_BANK_auth_free (&auth);
    296   }
    297 
    298   {
    299     enum GNUNET_DB_QueryStatus qs;
    300 
    301     TALER_MERCHANTDB_preflight (TMH_db);
    302     qs = TALER_MERCHANTDB_select_accounts_by_instance (
    303       TMH_db,
    304       mi->settings.id,
    305       &account_cb,
    306       &pac);
    307     switch (qs)
    308     {
    309     case GNUNET_DB_STATUS_HARD_ERROR:
    310       GNUNET_break (0);
    311       return TALER_MHD_reply_with_error (
    312         connection,
    313         MHD_HTTP_INTERNAL_SERVER_ERROR,
    314         TALER_EC_GENERIC_DB_FETCH_FAILED,
    315         "select_accounts");
    316     case GNUNET_DB_STATUS_SOFT_ERROR:
    317       GNUNET_break (0);
    318       return TALER_MHD_reply_with_error (
    319         connection,
    320         MHD_HTTP_INTERNAL_SERVER_ERROR,
    321         TALER_EC_GENERIC_DB_FETCH_FAILED,
    322         "select_accounts");
    323     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    324       break;
    325     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    326       break;
    327     }
    328 
    329     if (pac.have_same_account)
    330     {
    331       /* Idempotent request */
    332       return TALER_MHD_REPLY_JSON_PACK (
    333         connection,
    334         MHD_HTTP_OK,
    335         GNUNET_JSON_pack_data_auto (
    336           "salt",
    337           &pac.salt),
    338         GNUNET_JSON_pack_data_auto (
    339           "h_wire",
    340           &pac.h_wire));
    341     }
    342 
    343 #if 1
    344     if (pac.have_conflicting_account)
    345     {
    346       /* Conflict, refuse request */
    347       GNUNET_break_op (0);
    348       return TALER_MHD_reply_with_error (
    349         connection,
    350         MHD_HTTP_CONFLICT,
    351         TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
    352         pac.uri.full_payto);
    353     }
    354 #endif
    355 
    356     if (pac.have_any_account)
    357     {
    358       /* MFA needed */
    359       enum GNUNET_GenericReturnValue ret;
    360 
    361       ret = TMH_mfa_check_simple (
    362         hc,
    363         TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    364         mi);
    365       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    366                   "Account creation MFA check returned %d\n",
    367                   (int) ret);
    368       if (GNUNET_OK != ret)
    369       {
    370         return (GNUNET_NO == ret)
    371         ? MHD_YES
    372         : MHD_NO;
    373       }
    374     }
    375   }
    376 
    377   /* All pre-checks clear, now try to activate/setup the new account */
    378   {
    379     struct TMH_WireMethod *wm;
    380 
    381     /* convert provided payto URI into internal data structure with salts */
    382     wm = TMH_setup_wire_account (pac.uri,
    383                                  pac.credit_facade_url,
    384                                  pac.credit_facade_credentials);
    385     GNUNET_assert (NULL != wm);
    386     if (NULL != pac.extra_wire_subject_metadata)
    387       wm->extra_wire_subject_metadata
    388         = GNUNET_strdup (pac.extra_wire_subject_metadata);
    389     {
    390       struct TALER_MERCHANTDB_AccountDetails ad = {
    391         .payto_uri = wm->payto_uri,
    392         .salt = wm->wire_salt,
    393         .instance_id = mi->settings.id,
    394         .h_wire = wm->h_wire,
    395         .credit_facade_url = wm->credit_facade_url,
    396         .credit_facade_credentials = wm->credit_facade_credentials,
    397         .extra_wire_subject_metadata = wm->extra_wire_subject_metadata,
    398         .active = true
    399       };
    400       enum GNUNET_DB_QueryStatus qs;
    401       struct TALER_MerchantWireHashP h_wire;
    402       struct TALER_WireSaltP salt;
    403       bool not_found;
    404       bool conflict;
    405 
    406       TALER_MERCHANTDB_preflight (TMH_db);
    407       qs = TALER_MERCHANTDB_activate_account (TMH_db,
    408                                               &ad,
    409                                               &h_wire,
    410                                               &salt,
    411                                               &not_found,
    412                                               &conflict);
    413       switch (qs)
    414       {
    415       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    416         break;
    417       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    418         GNUNET_break (0);
    419         TMH_wire_method_free (wm);
    420         return TALER_MHD_reply_with_error (
    421           connection,
    422           MHD_HTTP_INTERNAL_SERVER_ERROR,
    423           TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    424           "activate_account");
    425       case GNUNET_DB_STATUS_SOFT_ERROR:
    426         GNUNET_break (0);
    427         TMH_wire_method_free (wm);
    428         return TALER_MHD_reply_with_error (
    429           connection,
    430           MHD_HTTP_INTERNAL_SERVER_ERROR,
    431           TALER_EC_GENERIC_DB_STORE_FAILED,
    432           "activate_account");
    433       case GNUNET_DB_STATUS_HARD_ERROR:
    434         GNUNET_break (0);
    435         TMH_wire_method_free (wm);
    436         return TALER_MHD_reply_with_error (
    437           connection,
    438           MHD_HTTP_INTERNAL_SERVER_ERROR,
    439           TALER_EC_GENERIC_DB_STORE_FAILED,
    440           "activate_account");
    441       }
    442       if (not_found)
    443       {
    444         /* must have been concurrently deleted, rare! */
    445         TMH_wire_method_free (wm);
    446         return TALER_MHD_reply_with_error (
    447           connection,
    448           MHD_HTTP_NOT_FOUND,
    449           TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    450           mi->settings.id);
    451       }
    452       if (conflict)
    453       {
    454         /* Conflicting POST must have been done between our pre-check
    455            and the actual transaction, rare! */
    456         TMH_wire_method_free (wm);
    457         GNUNET_break_op (0);
    458         return TALER_MHD_reply_with_error (
    459           connection,
    460           MHD_HTTP_CONFLICT,
    461           TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
    462           pac.uri.full_payto);
    463       }
    464 
    465       /* Update salt/h_wire in case we re-activated an
    466          existing account and found a different salt
    467          value already in the DB */
    468       wm->wire_salt = salt;
    469       wm->h_wire = h_wire;
    470 
    471       /* Finally, also update our running process */
    472       GNUNET_CONTAINER_DLL_insert (mi->wm_head,
    473                                    mi->wm_tail,
    474                                    wm);
    475       /* Note: we may not need to do this, as we notified
    476          about the account change above. But also hardly hurts. */
    477       TMH_reload_instances (mi->settings.id);
    478     }
    479     return TALER_MHD_REPLY_JSON_PACK (
    480       connection,
    481       MHD_HTTP_OK,
    482       GNUNET_JSON_pack_data_auto ("salt",
    483                                   &wm->wire_salt),
    484       GNUNET_JSON_pack_data_auto ("h_wire",
    485                                   &wm->h_wire));
    486   }
    487 }
    488 
    489 
    490 /* end of taler-merchant-httpd_post-private-accounts.c */