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: 100 170 58.8 %
Date: 2025-08-28 06:06:54 Functions: 2 3 66.7 %

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

Generated by: LCOV version 1.16