merchant

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

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


      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                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    303                                    true,
    304                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    305                                    is.phone,
    306                                    0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
    307                                          & TEH_mandatory_tan_channels)
    308                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    309                                    : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    310                                    mi->settings.email,
    311                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    312       break;
    313     case TMH_TCS_EMAIL:
    314       GNUNET_assert (NULL != is.email);
    315       is.email_validated = true;
    316       /* validate new e-mail address, if possible require old phone
    317          address for authorization */
    318       ret = TMH_mfa_challenges_do (hc,
    319                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    320                                    true,
    321                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    322                                    is.email,
    323                                    0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
    324                                          & TEH_mandatory_tan_channels)
    325                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    326                                    : TALER_MERCHANT_MFA_CHANNEL_SMS,
    327                                    mi->settings.phone,
    328                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    329       break;
    330     case TMH_TCS_EMAIL_AND_SMS:
    331       GNUNET_assert (NULL != is.phone);
    332       GNUNET_assert (NULL != is.email);
    333       is.phone_validated = true;
    334       is.email_validated = true;
    335       /* To change both, we require both old and both new
    336          addresses to consent */
    337       ret = TMH_mfa_challenges_do (hc,
    338                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    339                                    true,
    340                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    341                                    mi->settings.email,
    342                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    343                                    is.email,
    344                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    345                                    mi->settings.phone,
    346                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    347                                    is.phone,
    348                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    349       break;
    350     }
    351     if (GNUNET_OK != ret)
    352     {
    353       GNUNET_JSON_parse_free (spec);
    354       GNUNET_free (is.phone);
    355       return (GNUNET_NO == ret)
    356         ? MHD_YES
    357         : MHD_NO;
    358     }
    359   }
    360 
    361   for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
    362   {
    363     /* Cleanup after earlier loops */
    364     {
    365       struct TMH_WireMethod *wm;
    366 
    367       while (NULL != (wm = wm_head))
    368       {
    369         GNUNET_CONTAINER_DLL_remove (wm_head,
    370                                      wm_tail,
    371                                      wm);
    372         free_wm (wm);
    373       }
    374     }
    375     if (GNUNET_OK !=
    376         TALER_MERCHANTDB_start (TMH_db,
    377                                 "PATCH /instances"))
    378     {
    379       GNUNET_JSON_parse_free (spec);
    380       GNUNET_free (is.phone);
    381       return TALER_MHD_reply_with_error (connection,
    382                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    383                                          TALER_EC_GENERIC_DB_START_FAILED,
    384                                          NULL);
    385     }
    386     /* Check for equality of settings */
    387     if (! ( (0 == strcmp (mi->settings.name,
    388                           name)) &&
    389             ((mi->settings.email == is.email) ||
    390              (NULL != is.email && NULL != mi->settings.email &&
    391               0 == strcmp (mi->settings.email,
    392                            is.email))) &&
    393             ((mi->settings.phone == is.phone) ||
    394              (NULL != is.phone && NULL != mi->settings.phone &&
    395               0 == strcmp (mi->settings.phone,
    396                            is.phone))) &&
    397             ((mi->settings.website == is.website) ||
    398              (NULL != is.website && NULL != mi->settings.website &&
    399               0 == strcmp (mi->settings.website,
    400                            is.website))) &&
    401             ((mi->settings.logo == is.logo) ||
    402              (NULL != is.logo && NULL != mi->settings.logo &&
    403               0 == strcmp (mi->settings.logo,
    404                            is.logo))) &&
    405             (1 == json_equal (mi->settings.address,
    406                               is.address)) &&
    407             (1 == json_equal (mi->settings.jurisdiction,
    408                               is.jurisdiction)) &&
    409             (mi->settings.use_stefan == is.use_stefan) &&
    410             (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
    411                                        ==,
    412                                        is.default_wire_transfer_delay)) &&
    413             (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay,
    414                                        ==,
    415                                        is.default_refund_delay)) &&
    416             (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
    417                                        ==,
    418                                        is.default_pay_delay)) ) )
    419     {
    420       is.id = mi->settings.id;
    421       is.name = GNUNET_strdup (name);
    422       qs = TALER_MERCHANTDB_update_instance (TMH_db,
    423                                              &is);
    424       GNUNET_free (is.name);
    425       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    426       {
    427         TALER_MERCHANTDB_rollback (TMH_db);
    428         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    429           goto retry;
    430         else
    431           goto giveup;
    432       }
    433     }
    434     qs = TALER_MERCHANTDB_commit (TMH_db);
    435 retry:
    436     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    437       continue;
    438     break;
    439   } /* for(... MAX_RETRIES) */
    440 giveup:
    441   /* Update our 'settings' */
    442   GNUNET_free (mi->settings.name);
    443   GNUNET_free (mi->settings.email);
    444   GNUNET_free (mi->settings.phone);
    445   GNUNET_free (mi->settings.website);
    446   GNUNET_free (mi->settings.logo);
    447   json_decref (mi->settings.address);
    448   json_decref (mi->settings.jurisdiction);
    449   is.id = mi->settings.id;
    450   mi->settings = is;
    451   mi->settings.address = json_incref (mi->settings.address);
    452   mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
    453   mi->settings.name = GNUNET_strdup (name);
    454   if (NULL != is.email)
    455     mi->settings.email = GNUNET_strdup (is.email);
    456   mi->settings.phone = is.phone;
    457   is.phone = NULL;
    458   if (NULL != is.website)
    459     mi->settings.website = GNUNET_strdup (is.website);
    460   if (NULL != is.logo)
    461     mi->settings.logo = GNUNET_strdup (is.logo);
    462 
    463   GNUNET_JSON_parse_free (spec);
    464   TMH_reload_instances (mi->settings.id);
    465   return TALER_MHD_reply_static (connection,
    466                                  MHD_HTTP_NO_CONTENT,
    467                                  NULL,
    468                                  NULL,
    469                                  0);
    470 }
    471 
    472 
    473 enum MHD_Result
    474 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
    475                                 struct MHD_Connection *connection,
    476                                 struct TMH_HandlerContext *hc)
    477 {
    478   struct TMH_MerchantInstance *mi = hc->instance;
    479 
    480   return patch_instances_ID (mi,
    481                              true,
    482                              connection,
    483                              hc);
    484 }
    485 
    486 
    487 enum MHD_Result
    488 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
    489                                         struct MHD_Connection *connection,
    490                                         struct TMH_HandlerContext *hc)
    491 {
    492   struct TMH_MerchantInstance *mi;
    493 
    494   mi = TMH_lookup_instance (hc->infix);
    495   if (NULL == mi)
    496   {
    497     return TALER_MHD_reply_with_error (connection,
    498                                        MHD_HTTP_NOT_FOUND,
    499                                        TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    500                                        hc->infix);
    501   }
    502   if (mi->deleted)
    503   {
    504     return TALER_MHD_reply_with_error (connection,
    505                                        MHD_HTTP_CONFLICT,
    506                                        TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
    507                                        hc->infix);
    508   }
    509   return patch_instances_ID (mi,
    510                              false,
    511                              connection,
    512                              hc);
    513 }
    514 
    515 
    516 /* end of taler-merchant-httpd_patch-management-instances-INSTANCE.c */