LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-patch-instances-ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 47.3 % 182 86
Test Date: 2025-11-13 17:46:01 Functions: 50.0 % 4 2

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

Generated by: LCOV version 2.0-1