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: 45.9 % 194 89
Test Date: 2025-12-02 12:20:38 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            4 :   if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
     171              :   {
     172            0 :     GNUNET_break_op (0);
     173            0 :     GNUNET_JSON_parse_free (spec);
     174            0 :     return TALER_MHD_reply_with_error (connection,
     175              :                                        MHD_HTTP_BAD_REQUEST,
     176              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     177              :                                        "default_pay_delay");
     178              :   }
     179            4 :   if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
     180              :   {
     181            0 :     GNUNET_break_op (0);
     182            0 :     GNUNET_JSON_parse_free (spec);
     183            0 :     return TALER_MHD_reply_with_error (connection,
     184              :                                        MHD_HTTP_BAD_REQUEST,
     185              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     186              :                                        "default_refund_delay");
     187              :   }
     188            4 :   if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
     189              :   {
     190            0 :     GNUNET_break_op (0);
     191            0 :     GNUNET_JSON_parse_free (spec);
     192            0 :     return TALER_MHD_reply_with_error (connection,
     193              :                                        MHD_HTTP_BAD_REQUEST,
     194              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     195              :                                        "default_wire_transfer_delay");
     196              :   }
     197              : 
     198            4 :   if ( (NULL != is.phone) &&
     199            0 :        (NULL != mi->settings.phone) &&
     200            0 :        0 == strcmp (mi->settings.phone,
     201            0 :                     is.phone) )
     202            0 :     is.phone_validated = mi->settings.phone_validated;
     203            4 :   if ( (NULL != is.email) &&
     204            0 :        (NULL != mi->settings.email) &&
     205            0 :        0 == strcmp (mi->settings.email,
     206            0 :                     is.email) )
     207            0 :     is.email_validated = mi->settings.email_validated;
     208              :   {
     209            4 :     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
     210            4 :     enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels;
     211              : 
     212            4 :     if ( (0 != (mtc & TEH_TCS_SMS)) &&
     213            0 :          (NULL == is.phone) )
     214              :     {
     215            0 :       GNUNET_break_op (0);
     216            0 :       GNUNET_JSON_parse_free (spec);
     217            0 :       return TALER_MHD_reply_with_error (
     218              :         connection,
     219              :         MHD_HTTP_BAD_REQUEST,
     220              :         TALER_EC_GENERIC_PARAMETER_MISSING,
     221              :         "phone_number");
     222              :     }
     223            4 :     if ( (0 != (mtc & TEH_TCS_EMAIL)) &&
     224            0 :          (NULL == is.email) )
     225              :     {
     226            0 :       GNUNET_break_op (0);
     227            0 :       GNUNET_JSON_parse_free (spec);
     228            0 :       return TALER_MHD_reply_with_error (
     229              :         connection,
     230              :         MHD_HTTP_BAD_REQUEST,
     231              :         TALER_EC_GENERIC_PARAMETER_MISSING,
     232              :         "email");
     233              :     }
     234            4 :     if ( (is.phone_validated) &&
     235            0 :          (0 != (mtc & TEH_TCS_SMS)) )
     236            0 :       mtc -= TEH_TCS_SMS;
     237            4 :     if ( (is.email_validated) &&
     238            0 :          (0 != (mtc & TEH_TCS_EMAIL)) )
     239            0 :       mtc -= TEH_TCS_EMAIL;
     240            4 :     switch (mtc)
     241              :     {
     242            4 :     case TEH_TCS_NONE:
     243            4 :       ret = GNUNET_OK;
     244            4 :       break;
     245            0 :     case TEH_TCS_SMS:
     246            0 :       if (NULL == is.phone)
     247              :       {
     248            0 :         GNUNET_break_op (0);
     249            0 :         GNUNET_JSON_parse_free (spec);
     250            0 :         return TALER_MHD_reply_with_error (
     251              :           connection,
     252              :           MHD_HTTP_BAD_REQUEST,
     253              :           TALER_EC_GENERIC_PARAMETER_MISSING,
     254              :           "phone_number");
     255              :       }
     256            0 :       is.phone_validated = true;
     257              :       /* validate new phone number, if possible require old e-mail
     258              :          address for authorization */
     259            0 :       ret = TMH_mfa_challenges_do (hc,
     260              :                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
     261              :                                    true,
     262              :                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
     263              :                                    is.phone,
     264              :                                    0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
     265            0 :                                          & TEH_mandatory_tan_channels)
     266              :                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
     267              :                                    : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
     268              :                                    mi->settings.email,
     269              :                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
     270            0 :       break;
     271            0 :     case TEH_TCS_EMAIL:
     272            0 :       is.email_validated = true;
     273              :       /* validate new e-mail address, if possible require old phone
     274              :          address for authorization */
     275            0 :       ret = TMH_mfa_challenges_do (hc,
     276              :                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
     277              :                                    true,
     278              :                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
     279              :                                    is.email,
     280              :                                    0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
     281              :                                          & TEH_mandatory_tan_channels)
     282              :                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
     283              :                                    : TALER_MERCHANT_MFA_CHANNEL_SMS,
     284              :                                    mi->settings.phone,
     285              :                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
     286            0 :       break;
     287            0 :     case TEH_TCS_EMAIL_AND_SMS:
     288            0 :       is.phone_validated = true;
     289            0 :       is.email_validated = true;
     290              :       /* To change both, we require both old and both new
     291              :          addresses to consent */
     292            0 :       ret = TMH_mfa_challenges_do (hc,
     293              :                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
     294              :                                    true,
     295              :                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
     296              :                                    mi->settings.phone,
     297              :                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
     298              :                                    mi->settings.email,
     299              :                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
     300              :                                    is.phone,
     301              :                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
     302              :                                    is.email,
     303              :                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
     304            0 :       break;
     305              :     }
     306            4 :     if (GNUNET_OK != ret)
     307              :     {
     308            0 :       GNUNET_JSON_parse_free (spec);
     309              :       return (GNUNET_NO == ret)
     310              :         ? MHD_YES
     311            0 :         : MHD_NO;
     312              :     }
     313              : 
     314              :   }
     315              : 
     316            4 :   for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
     317              :   {
     318              :     /* Cleanup after earlier loops */
     319              :     {
     320              :       struct TMH_WireMethod *wm;
     321              : 
     322            4 :       while (NULL != (wm = wm_head))
     323              :       {
     324            0 :         GNUNET_CONTAINER_DLL_remove (wm_head,
     325              :                                      wm_tail,
     326              :                                      wm);
     327            0 :         free_wm (wm);
     328              :       }
     329              :     }
     330            4 :     if (GNUNET_OK !=
     331            4 :         TMH_db->start (TMH_db->cls,
     332              :                        "PATCH /instances"))
     333              :     {
     334            0 :       GNUNET_JSON_parse_free (spec);
     335            0 :       return TALER_MHD_reply_with_error (connection,
     336              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     337              :                                          TALER_EC_GENERIC_DB_START_FAILED,
     338              :                                          NULL);
     339              :     }
     340              :     /* Check for equality of settings */
     341            4 :     if (! ( (0 == strcmp (mi->settings.name,
     342            2 :                           name)) &&
     343            2 :             ((mi->settings.email == is.email) ||
     344            0 :              (NULL != is.email && NULL != mi->settings.email &&
     345            0 :               0 == strcmp (mi->settings.email,
     346            0 :                            is.email))) &&
     347            2 :             ((mi->settings.phone == is.phone) ||
     348            0 :              (NULL != is.phone && NULL != mi->settings.phone &&
     349            0 :               0 == strcmp (mi->settings.phone,
     350            0 :                            is.phone))) &&
     351            2 :             ((mi->settings.website == is.website) ||
     352            0 :              (NULL != is.website && NULL != mi->settings.website &&
     353            0 :               0 == strcmp (mi->settings.website,
     354            0 :                            is.website))) &&
     355            2 :             ((mi->settings.logo == is.logo) ||
     356            0 :              (NULL != is.logo && NULL != mi->settings.logo &&
     357            0 :               0 == strcmp (mi->settings.logo,
     358            2 :                            is.logo))) &&
     359            2 :             (1 == json_equal (mi->settings.address,
     360            2 :                               is.address)) &&
     361            0 :             (1 == json_equal (mi->settings.jurisdiction,
     362            0 :                               is.jurisdiction)) &&
     363            0 :             (mi->settings.use_stefan == is.use_stefan) &&
     364            0 :             (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
     365              :                                        ==,
     366              :                                        is.default_wire_transfer_delay)) &&
     367            0 :             (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
     368              :                                        ==,
     369              :                                        is.default_pay_delay)) ) )
     370              :     {
     371            4 :       is.id = mi->settings.id;
     372            4 :       is.name = GNUNET_strdup (name);
     373            4 :       qs = TMH_db->update_instance (TMH_db->cls,
     374              :                                     &is);
     375            4 :       GNUNET_free (is.name);
     376            4 :       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     377              :       {
     378            0 :         TMH_db->rollback (TMH_db->cls);
     379            0 :         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     380            0 :           goto retry;
     381              :         else
     382            0 :           goto giveup;
     383              :       }
     384              :     }
     385            4 :     qs = TMH_db->commit (TMH_db->cls);
     386            4 : retry:
     387            4 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     388            0 :       continue;
     389            4 :     break;
     390              :   } /* for(... MAX_RETRIES) */
     391            0 : giveup:
     392              :   /* Update our 'settings' */
     393            4 :   GNUNET_free (mi->settings.name);
     394            4 :   GNUNET_free (mi->settings.email);
     395            4 :   GNUNET_free (mi->settings.phone);
     396            4 :   GNUNET_free (mi->settings.website);
     397            4 :   GNUNET_free (mi->settings.logo);
     398            4 :   json_decref (mi->settings.address);
     399            4 :   json_decref (mi->settings.jurisdiction);
     400            4 :   is.id = mi->settings.id;
     401            4 :   mi->settings = is;
     402            4 :   mi->settings.address = json_incref (mi->settings.address);
     403            4 :   mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
     404            4 :   mi->settings.name = GNUNET_strdup (name);
     405            4 :   if (NULL != is.email)
     406            0 :     mi->settings.email = GNUNET_strdup (is.email);
     407            4 :   if (NULL != is.phone)
     408            0 :     mi->settings.phone = GNUNET_strdup (is.phone);
     409            4 :   if (NULL != is.website)
     410            0 :     mi->settings.website = GNUNET_strdup (is.website);
     411            4 :   if (NULL != is.logo)
     412            0 :     mi->settings.logo = GNUNET_strdup (is.logo);
     413              : 
     414            4 :   GNUNET_JSON_parse_free (spec);
     415            4 :   TMH_reload_instances (mi->settings.id);
     416            4 :   return TALER_MHD_reply_static (connection,
     417              :                                  MHD_HTTP_NO_CONTENT,
     418              :                                  NULL,
     419              :                                  NULL,
     420              :                                  0);
     421              : }
     422              : 
     423              : 
     424              : MHD_RESULT
     425            0 : TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
     426              :                                 struct MHD_Connection *connection,
     427              :                                 struct TMH_HandlerContext *hc)
     428              : {
     429            0 :   struct TMH_MerchantInstance *mi = hc->instance;
     430              : 
     431            0 :   return patch_instances_ID (mi,
     432              :                              connection,
     433              :                              hc);
     434              : }
     435              : 
     436              : 
     437              : MHD_RESULT
     438            4 : TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
     439              :                                         struct MHD_Connection *connection,
     440              :                                         struct TMH_HandlerContext *hc)
     441              : {
     442              :   struct TMH_MerchantInstance *mi;
     443              : 
     444            4 :   mi = TMH_lookup_instance (hc->infix);
     445            4 :   if (NULL == mi)
     446              :   {
     447            0 :     return TALER_MHD_reply_with_error (connection,
     448              :                                        MHD_HTTP_NOT_FOUND,
     449              :                                        TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
     450            0 :                                        hc->infix);
     451              :   }
     452            4 :   if (mi->deleted)
     453              :   {
     454            0 :     return TALER_MHD_reply_with_error (connection,
     455              :                                        MHD_HTTP_CONFLICT,
     456              :                                        TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
     457            0 :                                        hc->infix);
     458              :   }
     459            4 :   return patch_instances_ID (mi,
     460              :                              connection,
     461              :                              hc);
     462              : }
     463              : 
     464              : 
     465              : /* end of taler-merchant-httpd_private-patch-instances-ID.c */
        

Generated by: LCOV version 2.0-1