merchant

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

taler-merchant-httpd_patch-management-instances-INSTANCE.c (18679B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020-2025 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 taler-merchant-httpd_patch-management-instances-INSTANCE.c
     22  * @brief implementing PATCH /instances/$ID request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "taler/platform.h"
     26 #include "taler-merchant-httpd_patch-management-instances-INSTANCE.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 #include <taler/taler_dbevents.h>
     30 #include "taler-merchant-httpd_mfa.h"
     31 
     32 
     33 /**
     34  * How often do we retry the simple INSERT database transaction?
     35  */
     36 #define MAX_RETRIES 3
     37 
     38 
     39 /**
     40  * Free memory used by @a wm
     41  *
     42  * @param wm wire method to free
     43  */
     44 static void
     45 free_wm (struct TMH_WireMethod *wm)
     46 {
     47   GNUNET_free (wm->payto_uri.full_payto);
     48   GNUNET_free (wm->wire_method);
     49   GNUNET_free (wm);
     50 }
     51 
     52 
     53 /**
     54  * PATCH configuration of an existing instance, given its configuration.
     55  *
     56  * @param mi instance to patch
     57  * @param mfa_check true if a MFA check is required
     58  * @param connection the MHD connection to handle
     59  * @param[in,out] hc context with further information about the request
     60  * @return MHD result code
     61  */
     62 static MHD_RESULT
     63 patch_instances_ID (struct TMH_MerchantInstance *mi,
     64                     bool mfa_check,
     65                     struct MHD_Connection *connection,
     66                     struct TMH_HandlerContext *hc)
     67 {
     68   struct TALER_MERCHANTDB_InstanceSettings is;
     69   const char *name;
     70   struct TMH_WireMethod *wm_head = NULL;
     71   struct TMH_WireMethod *wm_tail = NULL;
     72   const char *iphone = NULL;
     73   bool no_transfer_delay;
     74   bool no_pay_delay;
     75   bool no_refund_delay;
     76   struct GNUNET_JSON_Specification spec[] = {
     77     GNUNET_JSON_spec_string ("name",
     78                              &name),
     79     GNUNET_JSON_spec_mark_optional (
     80       GNUNET_JSON_spec_string ("website",
     81                                (const char **) &is.website),
     82       NULL),
     83     GNUNET_JSON_spec_mark_optional (
     84       GNUNET_JSON_spec_string ("email",
     85                                (const char **) &is.email),
     86       NULL),
     87     GNUNET_JSON_spec_mark_optional (
     88       GNUNET_JSON_spec_string ("phone_number",
     89                                &iphone),
     90       NULL),
     91     GNUNET_JSON_spec_mark_optional (
     92       GNUNET_JSON_spec_string ("logo",
     93                                (const char **) &is.logo),
     94       NULL),
     95     GNUNET_JSON_spec_json ("address",
     96                            &is.address),
     97     GNUNET_JSON_spec_json ("jurisdiction",
     98                            &is.jurisdiction),
     99     GNUNET_JSON_spec_bool ("use_stefan",
    100                            &is.use_stefan),
    101     GNUNET_JSON_spec_mark_optional (
    102       GNUNET_JSON_spec_relative_time ("default_pay_delay",
    103                                       &is.default_pay_delay),
    104       &no_pay_delay),
    105     GNUNET_JSON_spec_mark_optional (
    106       GNUNET_JSON_spec_relative_time ("default_refund_delay",
    107                                       &is.default_refund_delay),
    108       &no_refund_delay),
    109     GNUNET_JSON_spec_mark_optional (
    110       GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
    111                                       &is.default_wire_transfer_delay),
    112       &no_transfer_delay),
    113     GNUNET_JSON_spec_mark_optional (
    114       GNUNET_JSON_spec_time_rounder_interval (
    115         "default_wire_transfer_rounding_interval",
    116         &is.default_wire_transfer_rounding_interval),
    117       NULL),
    118     GNUNET_JSON_spec_end ()
    119   };
    120   enum GNUNET_DB_QueryStatus qs;
    121 
    122   GNUNET_assert (NULL != mi);
    123   memset (&is,
    124           0,
    125           sizeof (is));
    126   {
    127     enum GNUNET_GenericReturnValue res;
    128 
    129     res = TALER_MHD_parse_json_data (connection,
    130                                      hc->request_body,
    131                                      spec);
    132     if (GNUNET_OK != res)
    133       return (GNUNET_NO == res)
    134              ? MHD_YES
    135              : MHD_NO;
    136   }
    137   if (! TMH_location_object_valid (is.address))
    138   {
    139     GNUNET_break_op (0);
    140     GNUNET_JSON_parse_free (spec);
    141     return TALER_MHD_reply_with_error (connection,
    142                                        MHD_HTTP_BAD_REQUEST,
    143                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    144                                        "address");
    145   }
    146   if ( (NULL != is.logo) &&
    147        (! TALER_MERCHANT_image_data_url_valid (is.logo)) )
    148   {
    149     GNUNET_break_op (0);
    150     GNUNET_JSON_parse_free (spec);
    151     return TALER_MHD_reply_with_error (connection,
    152                                        MHD_HTTP_BAD_REQUEST,
    153                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    154                                        "logo");
    155   }
    156 
    157   if (! TMH_location_object_valid (is.jurisdiction))
    158   {
    159     GNUNET_break_op (0);
    160     GNUNET_JSON_parse_free (spec);
    161     return TALER_MHD_reply_with_error (connection,
    162                                        MHD_HTTP_BAD_REQUEST,
    163                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    164                                        "jurisdiction");
    165   }
    166 
    167   if (no_transfer_delay)
    168     is.default_wire_transfer_delay = mi->settings.default_wire_transfer_delay;
    169   if (no_pay_delay)
    170     is.default_pay_delay = mi->settings.default_pay_delay;
    171   if (no_refund_delay)
    172     is.default_refund_delay = mi->settings.default_refund_delay;
    173   if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
    174   {
    175     GNUNET_break_op (0);
    176     GNUNET_JSON_parse_free (spec);
    177     return TALER_MHD_reply_with_error (connection,
    178                                        MHD_HTTP_BAD_REQUEST,
    179                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    180                                        "default_pay_delay");
    181   }
    182   if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
    183   {
    184     GNUNET_break_op (0);
    185     GNUNET_JSON_parse_free (spec);
    186     return TALER_MHD_reply_with_error (connection,
    187                                        MHD_HTTP_BAD_REQUEST,
    188                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    189                                        "default_refund_delay");
    190   }
    191   if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
    192   {
    193     GNUNET_break_op (0);
    194     GNUNET_JSON_parse_free (spec);
    195     return TALER_MHD_reply_with_error (connection,
    196                                        MHD_HTTP_BAD_REQUEST,
    197                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    198                                        "default_wire_transfer_delay");
    199   }
    200   if (NULL != iphone)
    201   {
    202     is.phone = TALER_MERCHANT_phone_validate_normalize (iphone,
    203                                                         false);
    204     if (NULL == is.phone)
    205     {
    206       GNUNET_break_op (0);
    207       GNUNET_JSON_parse_free (spec);
    208       return TALER_MHD_reply_with_error (connection,
    209                                          MHD_HTTP_BAD_REQUEST,
    210                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    211                                          "phone_number");
    212     }
    213     if ( (NULL != TMH_phone_regex) &&
    214          (0 !=
    215           regexec (&TMH_phone_rx,
    216                    is.phone,
    217                    0,
    218                    NULL,
    219                    0)) )
    220     {
    221       GNUNET_break_op (0);
    222       GNUNET_JSON_parse_free (spec);
    223       return TALER_MHD_reply_with_error (connection,
    224                                          MHD_HTTP_BAD_REQUEST,
    225                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    226                                          "phone_number");
    227     }
    228   }
    229   if ( (NULL != is.email) &&
    230        (! TALER_MERCHANT_email_valid (is.email)) )
    231   {
    232     GNUNET_break_op (0);
    233     GNUNET_JSON_parse_free (spec);
    234     GNUNET_free (is.phone);
    235     return TALER_MHD_reply_with_error (connection,
    236                                        MHD_HTTP_BAD_REQUEST,
    237                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    238                                        "email");
    239   }
    240   if ( (NULL != is.phone) &&
    241        (NULL != mi->settings.phone) &&
    242        (0 == strcmp (mi->settings.phone,
    243                      is.phone)) )
    244     is.phone_validated = mi->settings.phone_validated;
    245   if ( (NULL != is.email) &&
    246        (NULL != mi->settings.email) &&
    247        (0 == strcmp (mi->settings.email,
    248                      is.email)) )
    249     is.email_validated = mi->settings.email_validated;
    250   if (mfa_check)
    251   {
    252     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    253     enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels;
    254 
    255     if ( (0 != (mtc & TMH_TCS_SMS)) &&
    256          (NULL != mi->settings.phone) &&
    257          (NULL == is.phone) )
    258     {
    259       GNUNET_break_op (0);
    260       GNUNET_JSON_parse_free (spec);
    261       GNUNET_free (is.phone);
    262       return TALER_MHD_reply_with_error (
    263         connection,
    264         MHD_HTTP_BAD_REQUEST,
    265         TALER_EC_GENERIC_PARAMETER_MISSING,
    266         "phone_number");
    267     }
    268     if ( (0 != (mtc & TMH_TCS_EMAIL)) &&
    269          (NULL != mi->settings.email) &&
    270          (NULL == is.email) )
    271     {
    272       GNUNET_break_op (0);
    273       GNUNET_JSON_parse_free (spec);
    274       GNUNET_free (is.phone);
    275       return TALER_MHD_reply_with_error (
    276         connection,
    277         MHD_HTTP_BAD_REQUEST,
    278         TALER_EC_GENERIC_PARAMETER_MISSING,
    279         "email");
    280     }
    281     if ( (is.phone_validated ||
    282           (NULL == is.phone) ) &&
    283          (0 != (mtc & TMH_TCS_SMS)) )
    284       mtc -= TMH_TCS_SMS;
    285     if ( (is.email_validated ||
    286           (NULL == is.email) ) &&
    287          (0 != (mtc & TMH_TCS_EMAIL)) )
    288       mtc -= TMH_TCS_EMAIL;
    289     switch (mtc)
    290     {
    291     case TMH_TCS_NONE:
    292       ret = GNUNET_OK;
    293       break;
    294     case TMH_TCS_SMS:
    295       GNUNET_assert (NULL != is.phone);
    296       is.phone_validated = true;
    297       /* validate new phone number, if possible require old e-mail
    298          address for authorization */
    299       ret = TMH_mfa_challenges_do (hc,
    300                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    301                                    true,
    302                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    303                                    is.phone,
    304                                    0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
    305                                          & TEH_mandatory_tan_channels)
    306                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    307                                    : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    308                                    mi->settings.email,
    309                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    310       break;
    311     case TMH_TCS_EMAIL:
    312       GNUNET_assert (NULL != is.email);
    313       is.email_validated = true;
    314       /* validate new e-mail address, if possible require old phone
    315          address for authorization */
    316       ret = TMH_mfa_challenges_do (hc,
    317                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    318                                    true,
    319                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    320                                    is.email,
    321                                    0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
    322                                          & TEH_mandatory_tan_channels)
    323                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    324                                    : TALER_MERCHANT_MFA_CHANNEL_SMS,
    325                                    mi->settings.phone,
    326                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    327       break;
    328     case TMH_TCS_EMAIL_AND_SMS:
    329       GNUNET_assert (NULL != is.phone);
    330       GNUNET_assert (NULL != is.email);
    331       is.phone_validated = true;
    332       is.email_validated = true;
    333       /* To change both, we require both old and both new
    334          addresses to consent */
    335       ret = TMH_mfa_challenges_do (hc,
    336                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    337                                    true,
    338                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    339                                    mi->settings.phone,
    340                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    341                                    mi->settings.email,
    342                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    343                                    is.phone,
    344                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    345                                    is.email,
    346                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    347       break;
    348     }
    349     if (GNUNET_OK != ret)
    350     {
    351       GNUNET_JSON_parse_free (spec);
    352       GNUNET_free (is.phone);
    353       return (GNUNET_NO == ret)
    354         ? MHD_YES
    355         : MHD_NO;
    356     }
    357   }
    358 
    359   for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
    360   {
    361     /* Cleanup after earlier loops */
    362     {
    363       struct TMH_WireMethod *wm;
    364 
    365       while (NULL != (wm = wm_head))
    366       {
    367         GNUNET_CONTAINER_DLL_remove (wm_head,
    368                                      wm_tail,
    369                                      wm);
    370         free_wm (wm);
    371       }
    372     }
    373     if (GNUNET_OK !=
    374         TMH_db->start (TMH_db->cls,
    375                        "PATCH /instances"))
    376     {
    377       GNUNET_JSON_parse_free (spec);
    378       GNUNET_free (is.phone);
    379       return TALER_MHD_reply_with_error (connection,
    380                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    381                                          TALER_EC_GENERIC_DB_START_FAILED,
    382                                          NULL);
    383     }
    384     /* Check for equality of settings */
    385     if (! ( (0 == strcmp (mi->settings.name,
    386                           name)) &&
    387             ((mi->settings.email == is.email) ||
    388              (NULL != is.email && NULL != mi->settings.email &&
    389               0 == strcmp (mi->settings.email,
    390                            is.email))) &&
    391             ((mi->settings.phone == is.phone) ||
    392              (NULL != is.phone && NULL != mi->settings.phone &&
    393               0 == strcmp (mi->settings.phone,
    394                            is.phone))) &&
    395             ((mi->settings.website == is.website) ||
    396              (NULL != is.website && NULL != mi->settings.website &&
    397               0 == strcmp (mi->settings.website,
    398                            is.website))) &&
    399             ((mi->settings.logo == is.logo) ||
    400              (NULL != is.logo && NULL != mi->settings.logo &&
    401               0 == strcmp (mi->settings.logo,
    402                            is.logo))) &&
    403             (1 == json_equal (mi->settings.address,
    404                               is.address)) &&
    405             (1 == json_equal (mi->settings.jurisdiction,
    406                               is.jurisdiction)) &&
    407             (mi->settings.use_stefan == is.use_stefan) &&
    408             (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
    409                                        ==,
    410                                        is.default_wire_transfer_delay)) &&
    411             (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay,
    412                                        ==,
    413                                        is.default_refund_delay)) &&
    414             (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
    415                                        ==,
    416                                        is.default_pay_delay)) ) )
    417     {
    418       is.id = mi->settings.id;
    419       is.name = GNUNET_strdup (name);
    420       qs = TMH_db->update_instance (TMH_db->cls,
    421                                     &is);
    422       GNUNET_free (is.name);
    423       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    424       {
    425         TMH_db->rollback (TMH_db->cls);
    426         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    427           goto retry;
    428         else
    429           goto giveup;
    430       }
    431     }
    432     qs = TMH_db->commit (TMH_db->cls);
    433 retry:
    434     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    435       continue;
    436     break;
    437   } /* for(... MAX_RETRIES) */
    438 giveup:
    439   /* Update our 'settings' */
    440   GNUNET_free (mi->settings.name);
    441   GNUNET_free (mi->settings.email);
    442   GNUNET_free (mi->settings.phone);
    443   GNUNET_free (mi->settings.website);
    444   GNUNET_free (mi->settings.logo);
    445   json_decref (mi->settings.address);
    446   json_decref (mi->settings.jurisdiction);
    447   is.id = mi->settings.id;
    448   mi->settings = is;
    449   mi->settings.address = json_incref (mi->settings.address);
    450   mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
    451   mi->settings.name = GNUNET_strdup (name);
    452   if (NULL != is.email)
    453     mi->settings.email = GNUNET_strdup (is.email);
    454   mi->settings.phone = is.phone;
    455   is.phone = NULL;
    456   if (NULL != is.website)
    457     mi->settings.website = GNUNET_strdup (is.website);
    458   if (NULL != is.logo)
    459     mi->settings.logo = GNUNET_strdup (is.logo);
    460 
    461   GNUNET_JSON_parse_free (spec);
    462   TMH_reload_instances (mi->settings.id);
    463   return TALER_MHD_reply_static (connection,
    464                                  MHD_HTTP_NO_CONTENT,
    465                                  NULL,
    466                                  NULL,
    467                                  0);
    468 }
    469 
    470 
    471 MHD_RESULT
    472 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
    473                                 struct MHD_Connection *connection,
    474                                 struct TMH_HandlerContext *hc)
    475 {
    476   struct TMH_MerchantInstance *mi = hc->instance;
    477 
    478   return patch_instances_ID (mi,
    479                              true,
    480                              connection,
    481                              hc);
    482 }
    483 
    484 
    485 MHD_RESULT
    486 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
    487                                         struct MHD_Connection *connection,
    488                                         struct TMH_HandlerContext *hc)
    489 {
    490   struct TMH_MerchantInstance *mi;
    491 
    492   mi = TMH_lookup_instance (hc->infix);
    493   if (NULL == mi)
    494   {
    495     return TALER_MHD_reply_with_error (connection,
    496                                        MHD_HTTP_NOT_FOUND,
    497                                        TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    498                                        hc->infix);
    499   }
    500   if (mi->deleted)
    501   {
    502     return TALER_MHD_reply_with_error (connection,
    503                                        MHD_HTTP_CONFLICT,
    504                                        TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
    505                                        hc->infix);
    506   }
    507   return patch_instances_ID (mi,
    508                              false,
    509                              connection,
    510                              hc);
    511 }
    512 
    513 
    514 /* end of taler-merchant-httpd_patch-management-instances-INSTANCE.c */