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

Generated by: LCOV version 1.14