merchant

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

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


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