LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-post-instances.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 97 161 60.2 %
Date: 2025-06-23 16:22:09 Functions: 1 1 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2020-2023 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-post-instances.c
      22             :  * @brief implementing POST /instances request handling
      23             :  * @author Christian Grothoff
      24             :  */
      25             : #include "platform.h"
      26             : #include "taler-merchant-httpd_private-post-instances.h"
      27             : #include "taler-merchant-httpd_helper.h"
      28             : #include "taler_merchant_bank_lib.h"
      29             : #include <taler/taler_dbevents.h>
      30             : #include <taler/taler_json_lib.h>
      31             : #include <regex.h>
      32             : 
      33             : /**
      34             :  * How often do we retry the simple INSERT database transaction?
      35             :  */
      36             : #define MAX_RETRIES 3
      37             : 
      38             : 
      39             : /**
      40             :  * Generate an instance, given its configuration.
      41             :  *
      42             :  * @param rh context of the handler
      43             :  * @param connection the MHD connection to handle
      44             :  * @param[in,out] hc context with further information about the request
      45             :  * @return MHD result code
      46             :  */
      47             : MHD_RESULT
      48          31 : TMH_private_post_instances (const struct TMH_RequestHandler *rh,
      49             :                             struct MHD_Connection *connection,
      50             :                             struct TMH_HandlerContext *hc)
      51             : {
      52          31 :   struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
      53             :   struct TALER_MERCHANTDB_InstanceAuthSettings ias;
      54          31 :   const char *auth_password = NULL;
      55          31 :   const char *uts = "business";
      56          31 :   struct TMH_WireMethod *wm_head = NULL;
      57          31 :   struct TMH_WireMethod *wm_tail = NULL;
      58             :   const json_t *jauth;
      59             :   struct GNUNET_JSON_Specification spec[] = {
      60          31 :     GNUNET_JSON_spec_string ("id",
      61             :                              (const char **) &is.id),
      62          31 :     GNUNET_JSON_spec_string ("name",
      63             :                              (const char **) &is.name),
      64          31 :     GNUNET_JSON_spec_mark_optional (
      65             :       GNUNET_JSON_spec_string ("user_type",
      66             :                                &uts),
      67             :       NULL),
      68          31 :     GNUNET_JSON_spec_mark_optional (
      69             :       GNUNET_JSON_spec_string ("email",
      70             :                                (const char **) &is.email),
      71             :       NULL),
      72          31 :     GNUNET_JSON_spec_mark_optional (
      73             :       GNUNET_JSON_spec_string ("website",
      74             :                                (const char **) &is.website),
      75             :       NULL),
      76          31 :     GNUNET_JSON_spec_mark_optional (
      77             :       GNUNET_JSON_spec_string ("logo",
      78             :                                (const char **) &is.logo),
      79             :       NULL),
      80          31 :     GNUNET_JSON_spec_object_const ("auth",
      81             :                                    &jauth),
      82          31 :     GNUNET_JSON_spec_json ("address",
      83             :                            &is.address),
      84          31 :     GNUNET_JSON_spec_json ("jurisdiction",
      85             :                            &is.jurisdiction),
      86          31 :     GNUNET_JSON_spec_bool ("use_stefan",
      87             :                            &is.use_stefan),
      88          31 :     GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
      89             :                                     &is.default_wire_transfer_delay),
      90          31 :     GNUNET_JSON_spec_relative_time ("default_pay_delay",
      91             :                                     &is.default_pay_delay),
      92          31 :     GNUNET_JSON_spec_end ()
      93             :   };
      94             : 
      95             :   {
      96             :     enum GNUNET_GenericReturnValue res;
      97             : 
      98          31 :     res = TALER_MHD_parse_json_data (connection,
      99          31 :                                      hc->request_body,
     100             :                                      spec);
     101          31 :     if (GNUNET_OK != res)
     102             :       return (GNUNET_NO == res)
     103             :              ? MHD_YES
     104           0 :              : MHD_NO;
     105             :   }
     106             :   {
     107             :     enum GNUNET_GenericReturnValue ret;
     108             : 
     109          31 :     ret = TMH_check_auth_config (connection,
     110             :                                  jauth,
     111             :                                  &auth_password);
     112          31 :     if (GNUNET_OK != ret)
     113             :     {
     114           0 :       GNUNET_JSON_parse_free (spec);
     115           0 :       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
     116             :     }
     117             :   }
     118             : 
     119             :   /* check 'id' well-formed */
     120             :   {
     121             :     static bool once;
     122             :     static regex_t reg;
     123          31 :     bool id_wellformed = true;
     124             : 
     125          31 :     if (! once)
     126             :     {
     127          14 :       once = true;
     128          14 :       GNUNET_assert (0 ==
     129             :                      regcomp (&reg,
     130             :                               "^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
     131             :                               REG_EXTENDED));
     132             :     }
     133             : 
     134          31 :     if (0 != regexec (&reg,
     135          31 :                       is.id,
     136             :                       0, NULL, 0))
     137           0 :       id_wellformed = false;
     138          31 :     if (! id_wellformed)
     139             :     {
     140           0 :       GNUNET_JSON_parse_free (spec);
     141           0 :       return TALER_MHD_reply_with_error (connection,
     142             :                                          MHD_HTTP_BAD_REQUEST,
     143             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     144             :                                          "id");
     145             :     }
     146             :   }
     147             : 
     148          31 :   if (! TMH_location_object_valid (is.address))
     149             :   {
     150           0 :     GNUNET_break_op (0);
     151           0 :     GNUNET_JSON_parse_free (spec);
     152           0 :     return TALER_MHD_reply_with_error (connection,
     153             :                                        MHD_HTTP_BAD_REQUEST,
     154             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     155             :                                        "address");
     156             :   }
     157             : 
     158          31 :   if (! TMH_location_object_valid (is.jurisdiction))
     159             :   {
     160           0 :     GNUNET_break_op (0);
     161           0 :     GNUNET_JSON_parse_free (spec);
     162           0 :     return TALER_MHD_reply_with_error (connection,
     163             :                                        MHD_HTTP_BAD_REQUEST,
     164             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     165             :                                        "jurisdiction");
     166             :   }
     167             : 
     168          31 :   if ( (NULL != is.logo) &&
     169           0 :        (! TMH_image_data_url_valid (is.logo)) )
     170             :   {
     171           0 :     GNUNET_break_op (0);
     172           0 :     GNUNET_JSON_parse_free (spec);
     173           0 :     return TALER_MHD_reply_with_error (connection,
     174             :                                        MHD_HTTP_BAD_REQUEST,
     175             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     176             :                                        "logo");
     177             :   }
     178             : 
     179             :   {
     180             :     /* Test if an instance of this id is known */
     181             :     struct TMH_MerchantInstance *mi;
     182             : 
     183          31 :     mi = TMH_lookup_instance (is.id);
     184          31 :     if (NULL != mi)
     185             :     {
     186           2 :       if (mi->deleted)
     187             :       {
     188           0 :         GNUNET_JSON_parse_free (spec);
     189           0 :         return TALER_MHD_reply_with_error (connection,
     190             :                                            MHD_HTTP_CONFLICT,
     191             :                                            TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
     192           0 :                                            is.id);
     193             :       }
     194             :       /* Check for idempotency */
     195           2 :       if ( (0 == strcmp (mi->settings.id,
     196           2 :                          is.id)) &&
     197           2 :            (0 == strcmp (mi->settings.name,
     198           2 :                          is.name)) &&
     199           2 :            ((mi->settings.email == is.email) ||
     200           0 :             (NULL != is.email && NULL != mi->settings.email &&
     201           0 :              0 == strcmp (mi->settings.email,
     202           0 :                           is.email))) &&
     203           2 :            ((mi->settings.website == is.website) ||
     204           0 :             (NULL != is.website && NULL != mi->settings.website &&
     205           0 :              0 == strcmp (mi->settings.website,
     206           0 :                           is.website))) &&
     207           2 :            ((mi->settings.logo == is.logo) ||
     208           0 :             (NULL != is.logo && NULL != mi->settings.logo &&
     209           0 :              0 == strcmp (mi->settings.logo,
     210           0 :                           is.logo))) &&
     211           2 :            ( ( (NULL != auth_password) &&
     212             :                (GNUNET_OK ==
     213           0 :                 TMH_check_auth (auth_password,
     214             :                                 &mi->auth.auth_salt,
     215           2 :                                 &mi->auth.auth_hash)) ) ||
     216           4 :              ( (NULL == auth_password) &&
     217             :                (GNUNET_YES ==
     218           4 :                 GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
     219           2 :            (1 == json_equal (mi->settings.address,
     220           4 :                              is.address)) &&
     221           2 :            (1 == json_equal (mi->settings.jurisdiction,
     222           2 :                              is.jurisdiction)) &&
     223           2 :            (mi->settings.use_stefan == is.use_stefan) &&
     224           2 :            (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
     225             :                                       ==,
     226           2 :                                       is.default_wire_transfer_delay)) &&
     227           2 :            (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
     228             :                                       ==,
     229             :                                       is.default_pay_delay)) )
     230             :       {
     231           2 :         GNUNET_JSON_parse_free (spec);
     232           2 :         return TALER_MHD_reply_static (connection,
     233             :                                        MHD_HTTP_NO_CONTENT,
     234             :                                        NULL,
     235             :                                        NULL,
     236             :                                        0);
     237             :       }
     238           0 :       GNUNET_JSON_parse_free (spec);
     239           0 :       return TALER_MHD_reply_with_error (connection,
     240             :                                          MHD_HTTP_CONFLICT,
     241             :                                          TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
     242           0 :                                          is.id);
     243             :     }
     244             :   }
     245             : 
     246             :   /* handle authentication token setup */
     247          29 :   if (NULL == auth_password)
     248             :   {
     249          23 :     memset (&ias.auth_salt,
     250             :             0,
     251             :             sizeof (ias.auth_salt));
     252          23 :     memset (&ias.auth_hash,
     253             :             0,
     254             :             sizeof (ias.auth_hash));
     255             :   }
     256             :   else
     257             :   {
     258             :     /* Sets 'auth_salt' and 'auth_hash' */
     259           6 :     TMH_compute_auth (auth_password,
     260             :                       &ias.auth_salt,
     261             :                       &ias.auth_hash);
     262             :   }
     263             : 
     264             :   /* create in-memory data structure */
     265             :   {
     266             :     struct TMH_MerchantInstance *mi;
     267             :     enum GNUNET_DB_QueryStatus qs;
     268             : 
     269          29 :     mi = GNUNET_new (struct TMH_MerchantInstance);
     270          29 :     mi->wm_head = wm_head;
     271          29 :     mi->wm_tail = wm_tail;
     272          29 :     mi->settings = is;
     273          29 :     mi->settings.address = json_incref (mi->settings.address);
     274          29 :     mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
     275          29 :     mi->settings.id = GNUNET_strdup (is.id);
     276          29 :     mi->settings.name = GNUNET_strdup (is.name);
     277          29 :     if (NULL != is.email)
     278           0 :       mi->settings.email = GNUNET_strdup (is.email);
     279          29 :     if (NULL != is.website)
     280           0 :       mi->settings.website = GNUNET_strdup (is.website);
     281          29 :     if (NULL != is.logo)
     282           0 :       mi->settings.logo = GNUNET_strdup (is.logo);
     283          29 :     mi->auth = ias;
     284          29 :     GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
     285          29 :     GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
     286             :                                         &mi->merchant_pub.eddsa_pub);
     287             : 
     288          29 :     for (unsigned int i = 0; i<MAX_RETRIES; i++)
     289             :     {
     290          29 :       if (GNUNET_OK !=
     291          29 :           TMH_db->start (TMH_db->cls,
     292             :                          "post /instances"))
     293             :       {
     294           0 :         mi->rc = 1;
     295           0 :         TMH_instance_decref (mi);
     296           0 :         GNUNET_JSON_parse_free (spec);
     297           0 :         return TALER_MHD_reply_with_error (connection,
     298             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     299             :                                            TALER_EC_GENERIC_DB_START_FAILED,
     300             :                                            NULL);
     301             :       }
     302          29 :       qs = TMH_db->insert_instance (TMH_db->cls,
     303          29 :                                     &mi->merchant_pub,
     304          29 :                                     &mi->merchant_priv,
     305          29 :                                     &mi->settings,
     306          29 :                                     &mi->auth);
     307          29 :       switch (qs)
     308             :       {
     309           0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     310             :         {
     311             :           MHD_RESULT ret;
     312             : 
     313           0 :           TMH_db->rollback (TMH_db->cls);
     314           0 :           GNUNET_break (0);
     315           0 :           ret = TALER_MHD_reply_with_error (connection,
     316             :                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
     317             :                                             TALER_EC_GENERIC_DB_STORE_FAILED,
     318           0 :                                             is.id);
     319           0 :           mi->rc = 1;
     320           0 :           TMH_instance_decref (mi);
     321           0 :           GNUNET_JSON_parse_free (spec);
     322           0 :           return ret;
     323             :         }
     324           0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     325           0 :         goto retry;
     326           0 :       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     327             :         {
     328             :           MHD_RESULT ret;
     329             : 
     330           0 :           TMH_db->rollback (TMH_db->cls);
     331           0 :           GNUNET_break (0);
     332           0 :           ret = TALER_MHD_reply_with_error (connection,
     333             :                                             MHD_HTTP_CONFLICT,
     334             :                                             TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
     335           0 :                                             is.id);
     336           0 :           mi->rc = 1;
     337           0 :           TMH_instance_decref (mi);
     338           0 :           GNUNET_JSON_parse_free (spec);
     339           0 :           return ret;
     340             :         }
     341          29 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     342             :         /* handled below */
     343          29 :         break;
     344             :       }
     345          29 :       qs = TMH_db->commit (TMH_db->cls);
     346          29 :       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     347          29 :         qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     348           0 : retry:
     349          29 :       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     350          29 :         break; /* success! -- or hard failure */
     351             :     } /* for .. MAX_RETRIES */
     352          29 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     353             :     {
     354           0 :       mi->rc = 1;
     355           0 :       TMH_instance_decref (mi);
     356           0 :       GNUNET_JSON_parse_free (spec);
     357           0 :       return TALER_MHD_reply_with_error (connection,
     358             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     359             :                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
     360             :                                          NULL);
     361             :     }
     362             :     /* Finally, also update our running process */
     363          29 :     GNUNET_assert (GNUNET_OK ==
     364             :                    TMH_add_instance (mi));
     365          29 :     TMH_reload_instances (mi->settings.id);
     366             :   }
     367          29 :   GNUNET_JSON_parse_free (spec);
     368          29 :   return TALER_MHD_reply_static (connection,
     369             :                                  MHD_HTTP_NO_CONTENT,
     370             :                                  NULL,
     371             :                                  NULL,
     372             :                                  0);
     373             : }
     374             : 
     375             : 
     376             : /* end of taler-merchant-httpd_private-post-instances.c */

Generated by: LCOV version 1.16