LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-post-instances.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 89 222 40.1 %
Date: 2022-06-30 06:15:34 Functions: 1 4 25.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2020, 2021 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/taler_json_lib.h>
      29             : #include <regex.h>
      30             : 
      31             : /**
      32             :  * How often do we retry the simple INSERT database transaction?
      33             :  */
      34             : #define MAX_RETRIES 3
      35             : 
      36             : 
      37             : /**
      38             :  * Check if the array of @a payto_uris contains exactly the same
      39             :  * URIs as those already in @a mi (possibly in a different order).
      40             :  *
      41             :  * @param mi a merchant instance with accounts
      42             :  * @param payto_uris a JSON array with accounts (presumably)
      43             :  * @return true if they are 'equal', false if not or of payto_uris is not an array
      44             :  */
      45             : static bool
      46           0 : accounts_equal (const struct TMH_MerchantInstance *mi,
      47             :                 json_t *payto_uris)
      48             : {
      49           0 :   if (! json_is_array (payto_uris))
      50           0 :     return false;
      51           0 :   {
      52           0 :     unsigned int len = json_array_size (payto_uris);
      53           0 :     bool matches[GNUNET_NZL (len)];
      54             :     struct TMH_WireMethod *wm;
      55             : 
      56           0 :     memset (matches,
      57             :             0,
      58           0 :             sizeof (matches));
      59           0 :     for (wm = mi->wm_head;
      60             :          NULL != wm;
      61           0 :          wm = wm->next)
      62             :     {
      63           0 :       const char *uri = wm->payto_uri;
      64             : 
      65           0 :       GNUNET_assert (NULL != uri);
      66           0 :       for (unsigned int i = 0; i<len; i++)
      67             :       {
      68           0 :         const char *str = json_string_value (json_array_get (payto_uris,
      69             :                                                              i));
      70             : 
      71           0 :         GNUNET_assert (NULL != str);
      72           0 :         if (0 == strcasecmp (uri,
      73             :                              str))
      74             :         {
      75           0 :           if (matches[i])
      76             :           {
      77           0 :             GNUNET_break (0);
      78           0 :             return false; /* duplicate entry!? */
      79             :           }
      80           0 :           matches[i] = true;
      81           0 :           break;
      82             :         }
      83             :       }
      84             :     }
      85           0 :     for (unsigned int i = 0; i<len; i++)
      86           0 :       if (! matches[i])
      87           0 :         return false;
      88             :   }
      89           0 :   return true;
      90             : }
      91             : 
      92             : 
      93             : /**
      94             :  * Free memory used by @a wm
      95             :  *
      96             :  * @param wm wire method to free
      97             :  */
      98             : static void
      99           0 : free_wm (struct TMH_WireMethod *wm)
     100             : {
     101           0 :   GNUNET_free (wm->payto_uri);
     102           0 :   GNUNET_free (wm->wire_method);
     103           0 :   GNUNET_free (wm);
     104           0 : }
     105             : 
     106             : 
     107             : /**
     108             :  * Free memory used by @a mi.
     109             :  *
     110             :  * @param mi instance to free
     111             :  */
     112             : static void
     113           0 : free_mi (struct TMH_MerchantInstance *mi)
     114             : {
     115             :   struct TMH_WireMethod *wm;
     116             : 
     117           0 :   while (NULL != (wm = mi->wm_head))
     118             :   {
     119           0 :     GNUNET_CONTAINER_DLL_remove (mi->wm_head,
     120             :                                  mi->wm_tail,
     121             :                                  wm);
     122           0 :     free_wm (wm);
     123             :   }
     124           0 :   GNUNET_free (mi->settings.id);
     125           0 :   GNUNET_free (mi->settings.name);
     126           0 :   GNUNET_free (mi->settings.website);
     127           0 :   GNUNET_free (mi->settings.email);
     128           0 :   GNUNET_free (mi->settings.logo);
     129           0 :   json_decref (mi->settings.address);
     130           0 :   json_decref (mi->settings.jurisdiction);
     131           0 :   GNUNET_free (mi);
     132           0 : }
     133             : 
     134             : 
     135             : /**
     136             :  * Generate an instance, given its configuration.
     137             :  *
     138             :  * @param rh context of the handler
     139             :  * @param connection the MHD connection to handle
     140             :  * @param[in,out] hc context with further information about the request
     141             :  * @return MHD result code
     142             :  */
     143             : MHD_RESULT
     144           6 : TMH_private_post_instances (const struct TMH_RequestHandler *rh,
     145             :                             struct MHD_Connection *connection,
     146             :                             struct TMH_HandlerContext *hc)
     147             : {
     148             :   struct TALER_MERCHANTDB_InstanceSettings is;
     149             :   struct TALER_MERCHANTDB_InstanceAuthSettings ias;
     150             :   json_t *payto_uris;
     151           6 :   const char *auth_token = NULL;
     152           6 :   struct TMH_WireMethod *wm_head = NULL;
     153           6 :   struct TMH_WireMethod *wm_tail = NULL;
     154             :   json_t *jauth;
     155             :   struct GNUNET_JSON_Specification spec[] = {
     156           6 :     GNUNET_JSON_spec_json ("payto_uris",
     157             :                            &payto_uris),
     158           6 :     GNUNET_JSON_spec_string ("id",
     159             :                              (const char **) &is.id),
     160           6 :     GNUNET_JSON_spec_string ("name",
     161             :                              (const char **) &is.name),
     162           6 :     GNUNET_JSON_spec_mark_optional(
     163             :       GNUNET_JSON_spec_string ("email",
     164             :                                (const char **) &is.email),
     165             :       NULL),
     166           6 :     GNUNET_JSON_spec_mark_optional(
     167             :       GNUNET_JSON_spec_string ("website",
     168             :                                (const char **) &is.website),
     169             :       NULL),
     170           6 :     GNUNET_JSON_spec_mark_optional(
     171             :       GNUNET_JSON_spec_string ("logo",
     172             :                                (const char **) &is.logo),
     173             :       NULL),
     174           6 :     GNUNET_JSON_spec_json ("auth",
     175             :                            &jauth),
     176           6 :     GNUNET_JSON_spec_json ("address",
     177             :                            &is.address),
     178           6 :     GNUNET_JSON_spec_json ("jurisdiction",
     179             :                            &is.jurisdiction),
     180           6 :     TALER_JSON_spec_amount ("default_max_wire_fee",
     181             :                             TMH_currency,
     182             :                             &is.default_max_wire_fee),
     183           6 :     GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
     184             :                              &is.default_wire_fee_amortization),
     185           6 :     TALER_JSON_spec_amount ("default_max_deposit_fee",
     186             :                             TMH_currency,
     187             :                             &is.default_max_deposit_fee),
     188           6 :     GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
     189             :                                     &is.default_wire_transfer_delay),
     190           6 :     GNUNET_JSON_spec_relative_time ("default_pay_delay",
     191             :                                     &is.default_pay_delay),
     192           6 :     GNUNET_JSON_spec_end ()
     193             :   };
     194             : 
     195             :   {
     196             :     enum GNUNET_GenericReturnValue res;
     197             : 
     198           6 :     res = TALER_MHD_parse_json_data (connection,
     199           6 :                                      hc->request_body,
     200             :                                      spec);
     201           6 :     if (GNUNET_OK != res)
     202             :       return (GNUNET_NO == res)
     203             :              ? MHD_YES
     204           0 :              : MHD_NO;
     205             :   }
     206             : 
     207             :   {
     208             :     enum GNUNET_GenericReturnValue ret;
     209             : 
     210           6 :     ret = TMH_check_auth_config (connection,
     211             :                                  jauth,
     212             :                                  &auth_token);
     213           6 :     if (GNUNET_OK != ret)
     214           0 :       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
     215             :   }
     216             : 
     217             :   /* check payto_uris for well-formedness */
     218           6 :   if (! TMH_payto_uri_array_valid (payto_uris))
     219           0 :     return TALER_MHD_reply_with_error (connection,
     220             :                                        MHD_HTTP_BAD_REQUEST,
     221             :                                        TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
     222             :                                        NULL);
     223             : 
     224             :   /* check 'id' well-formed */
     225             :   {
     226             :     static bool once;
     227             :     static regex_t reg;
     228           6 :     bool id_wellformed = true;
     229             : 
     230           6 :     if (! once)
     231             :     {
     232           6 :       GNUNET_assert (0 ==
     233             :                      regcomp (&reg,
     234             :                               "^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
     235             :                               REG_EXTENDED));
     236             :     }
     237             : 
     238           6 :     if (0 != regexec (&reg,
     239           6 :                       is.id,
     240             :                       0, NULL, 0))
     241           0 :       id_wellformed = false;
     242           6 :     if (! id_wellformed)
     243           0 :       return TALER_MHD_reply_with_error (connection,
     244             :                                          MHD_HTTP_BAD_REQUEST,
     245             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     246             :                                          "id");
     247             :   }
     248             : 
     249           6 :   if (! TMH_location_object_valid (is.address))
     250             :   {
     251           0 :     GNUNET_break_op (0);
     252           0 :     GNUNET_JSON_parse_free (spec);
     253           0 :     return TALER_MHD_reply_with_error (connection,
     254             :                                        MHD_HTTP_BAD_REQUEST,
     255             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     256             :                                        "address");
     257             :   }
     258             : 
     259           6 :   if (! TMH_location_object_valid (is.jurisdiction))
     260             :   {
     261           0 :     GNUNET_break_op (0);
     262           0 :     GNUNET_JSON_parse_free (spec);
     263           0 :     return TALER_MHD_reply_with_error (connection,
     264             :                                        MHD_HTTP_BAD_REQUEST,
     265             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     266             :                                        "jurisdiction");
     267             :   }
     268             : 
     269           6 :   if ( (NULL != is.logo) &&
     270           0 :        (! TMH_image_data_url_valid (is.logo)) )
     271             :   {
     272           0 :     GNUNET_break_op (0);
     273           0 :     GNUNET_JSON_parse_free (spec);
     274           0 :     return TALER_MHD_reply_with_error (connection,
     275             :                                        MHD_HTTP_BAD_REQUEST,
     276             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     277             :                                        "logo");
     278             :   }
     279             : 
     280             :   {
     281             :     /* Test if an instance of this id is known */
     282             :     struct TMH_MerchantInstance *mi;
     283             : 
     284           6 :     mi = TMH_lookup_instance (is.id);
     285           6 :     if (NULL != mi)
     286             :     {
     287           0 :       if (mi->deleted)
     288             :       {
     289           0 :         GNUNET_JSON_parse_free (spec);
     290           0 :         return TALER_MHD_reply_with_error (connection,
     291             :                                            MHD_HTTP_CONFLICT,
     292             :                                            TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
     293           0 :                                            is.id);
     294             :       }
     295             :       /* Check for idempotency */
     296           0 :       if ( (0 == strcmp (mi->settings.id,
     297           0 :                          is.id)) &&
     298           0 :            (0 == strcmp (mi->settings.name,
     299           0 :                          is.name)) &&
     300           0 :             ((mi->settings.email == is.email) ||
     301           0 :              (NULL != is.email && NULL != mi->settings.email &&
     302           0 :               0 == strcmp (mi->settings.email,
     303           0 :                             is.email))) &&
     304           0 :             ((mi->settings.website == is.website) ||
     305           0 :              (NULL != is.website && NULL != mi->settings.website &&
     306           0 :               0 == strcmp (mi->settings.website,
     307           0 :                             is.website))) &&
     308           0 :             ((mi->settings.logo == is.logo) ||
     309           0 :              (NULL != is.logo && NULL != mi->settings.logo &&
     310           0 :               0 == strcmp (mi->settings.logo,
     311           0 :                             is.logo))) &&
     312           0 :            ( ( (NULL != auth_token) &&
     313             :                (GNUNET_OK ==
     314           0 :                 TMH_check_auth (auth_token,
     315             :                                 &mi->auth.auth_salt,
     316           0 :                                 &mi->auth.auth_hash)) ) ||
     317           0 :              ( (NULL == auth_token) &&
     318             :                (GNUNET_YES ==
     319           0 :                 GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
     320           0 :            (1 == json_equal (mi->settings.address,
     321           0 :                              is.address)) &&
     322           0 :            (1 == json_equal (mi->settings.jurisdiction,
     323           0 :                              is.jurisdiction)) &&
     324           0 :            (GNUNET_OK == TALER_amount_cmp_currency (
     325           0 :               &mi->settings.default_max_deposit_fee,
     326           0 :               &is.default_max_deposit_fee)) &&
     327           0 :            (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
     328           0 :                                    &is.default_max_deposit_fee)) &&
     329           0 :            (GNUNET_OK == TALER_amount_cmp_currency (
     330           0 :               &mi->settings.default_max_wire_fee,
     331           0 :               &is.default_max_wire_fee)) &&
     332           0 :            (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
     333           0 :                                    &is.default_max_wire_fee)) &&
     334           0 :            (mi->settings.default_wire_fee_amortization ==
     335           0 :             is.default_wire_fee_amortization) &&
     336           0 :            (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
     337             :                                       ==,
     338           0 :                                       is.default_wire_transfer_delay)) &&
     339           0 :            (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
     340             :                                       ==,
     341           0 :                                       is.default_pay_delay)) &&
     342           0 :            (accounts_equal (mi,
     343             :                             payto_uris)) )
     344             :       {
     345           0 :         GNUNET_JSON_parse_free (spec);
     346           0 :         return TALER_MHD_reply_static (connection,
     347             :                                        MHD_HTTP_NO_CONTENT,
     348             :                                        NULL,
     349             :                                        NULL,
     350             :                                        0);
     351             :       }
     352             :       else
     353             :       {
     354           0 :         GNUNET_JSON_parse_free (spec);
     355           0 :         return TALER_MHD_reply_with_error (connection,
     356             :                                            MHD_HTTP_CONFLICT,
     357             :                                            TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
     358           0 :                                            is.id);
     359             :       }
     360             :     }
     361             :   }
     362             : 
     363             :   /* convert provided payto URIs into internal data structure with salts */
     364             :   {
     365           6 :     unsigned int len = json_array_size (payto_uris);
     366             : 
     367          12 :     for (unsigned int i = 0; i<len; i++)
     368             :     {
     369           6 :       json_t *payto_uri = json_array_get (payto_uris,
     370             :                                           i);
     371             :       struct TMH_WireMethod *wm;
     372             : 
     373           6 :       wm = TMH_setup_wire_account (json_string_value (payto_uri));
     374           6 :       GNUNET_CONTAINER_DLL_insert (wm_head,
     375             :                                    wm_tail,
     376             :                                    wm);
     377             :     }
     378             :   }
     379             : 
     380             :   /* handle authentication token setup */
     381           6 :   if (NULL == auth_token)
     382             :   {
     383           4 :     memset (&ias.auth_salt,
     384             :             0,
     385             :             sizeof (ias.auth_salt));
     386           4 :     memset (&ias.auth_hash,
     387             :             0,
     388             :             sizeof (ias.auth_hash));
     389             :   }
     390             :   else
     391             :   {
     392             :     /* Sets 'auth_salt' and 'auth_hash' */
     393           2 :     TMH_compute_auth (auth_token,
     394             :                       &ias.auth_salt,
     395             :                       &ias.auth_hash);
     396             :   }
     397             : 
     398             :   /* create in-memory data structure */
     399             :   {
     400             :     struct TMH_MerchantInstance *mi;
     401             :     enum GNUNET_DB_QueryStatus qs;
     402             : 
     403           6 :     mi = GNUNET_new (struct TMH_MerchantInstance);
     404           6 :     mi->wm_head = wm_head;
     405           6 :     mi->wm_tail = wm_tail;
     406           6 :     mi->settings = is;
     407           6 :     mi->settings.address = json_incref (mi->settings.address);
     408           6 :     mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
     409           6 :     mi->settings.id = GNUNET_strdup (is.id);
     410           6 :     mi->settings.name = GNUNET_strdup (is.name);
     411           6 :     if (NULL != is.email)
     412           0 :       mi->settings.email = GNUNET_strdup (is.email);
     413           6 :     if (NULL != is.website)
     414           0 :       mi->settings.website = GNUNET_strdup (is.website);
     415           6 :     if (NULL != is.logo)
     416           0 :       mi->settings.logo = GNUNET_strdup (is.logo);
     417           6 :     mi->auth = ias;
     418           6 :     GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
     419           6 :     GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
     420             :                                         &mi->merchant_pub.eddsa_pub);
     421             : 
     422           6 :     for (unsigned int i = 0; i<MAX_RETRIES; i++)
     423             :     {
     424           6 :       if (GNUNET_OK !=
     425           6 :           TMH_db->start (TMH_db->cls,
     426             :                          "post /instances"))
     427             :       {
     428           0 :         GNUNET_JSON_parse_free (spec);
     429           0 :         free_mi (mi);
     430           0 :         return TALER_MHD_reply_with_error (connection,
     431             :                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
     432             :                                            TALER_EC_GENERIC_DB_START_FAILED,
     433             :                                            NULL);
     434             :       }
     435           6 :       qs = TMH_db->insert_instance (TMH_db->cls,
     436           6 :                                     &mi->merchant_pub,
     437           6 :                                     &mi->merchant_priv,
     438           6 :                                     &mi->settings,
     439           6 :                                     &mi->auth);
     440           6 :       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     441             :       {
     442             :         MHD_RESULT ret;
     443             : 
     444           0 :         TMH_db->rollback (TMH_db->cls);
     445           0 :         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     446           0 :           goto retry;
     447           0 :         ret = TALER_MHD_reply_with_error (connection,
     448             :                                           MHD_HTTP_CONFLICT,
     449             :                                           TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
     450           0 :                                           is.id);
     451           0 :         GNUNET_JSON_parse_free (spec);
     452           0 :         free_mi (mi);
     453           0 :         return ret;
     454             :       }
     455          12 :       for (struct TMH_WireMethod *wm = wm_head;
     456             :            NULL != wm;
     457           6 :            wm = wm->next)
     458             :       {
     459           6 :         struct TALER_MERCHANTDB_AccountDetails ad = {
     460           6 :           .payto_uri = wm->payto_uri,
     461             :           .salt = wm->wire_salt,
     462             :           .h_wire = wm->h_wire,
     463           6 :           .active = wm->active
     464             :         };
     465             : 
     466           6 :         qs = TMH_db->insert_account (TMH_db->cls,
     467           6 :                                      mi->settings.id,
     468             :                                      &ad);
     469           6 :         if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     470           0 :           break;
     471             :       }
     472           6 :       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     473             :       {
     474           0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     475           0 :         TMH_db->rollback (TMH_db->cls);
     476           0 :         if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     477           0 :           break;
     478           0 :         goto retry;
     479             :       }
     480           6 :       qs = TMH_db->commit (TMH_db->cls);
     481           6 :       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     482           6 :         qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     483           0 : retry:
     484           6 :       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     485           6 :         break; /* success! -- or hard failure */
     486             :     } /* for .. MAX_RETRIES */
     487           6 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     488             :     {
     489           0 :       GNUNET_JSON_parse_free (spec);
     490           0 :       free_mi (mi);
     491           0 :       return TALER_MHD_reply_with_error (connection,
     492             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     493             :                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
     494             :                                          NULL);
     495             :     }
     496             :     /* Finally, also update our running process */
     497           6 :     GNUNET_assert (GNUNET_OK ==
     498             :                    TMH_add_instance (mi));
     499           6 :     TMH_reload_instances (mi->settings.id);
     500             :   }
     501           6 :   GNUNET_JSON_parse_free (spec);
     502           6 :   if (0 == strcmp (is.id,
     503             :                    "default"))
     504             :   {
     505           3 :     GNUNET_free (TMH_default_auth); /* clear it if the default instance was
     506             :                                        created */
     507             :   }
     508             : 
     509           6 :   return TALER_MHD_reply_static (connection,
     510             :                                  MHD_HTTP_NO_CONTENT,
     511             :                                  NULL,
     512             :                                  NULL,
     513             :                                  0);
     514             : }
     515             : 
     516             : 
     517             : /* end of taler-merchant-httpd_private-post-instances.c */

Generated by: LCOV version 1.14