LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-private-tokenfamilies.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 38.5 % 156 60
Test Date: 2026-09-04 23:42:01 Functions: 50.0 % 2 1

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2023, 2024 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 src/backend/taler-merchant-httpd_post-private-tokenfamilies.c
      22              :  * @brief implementing POST /tokenfamilies request handling
      23              :  * @author Christian Blättler
      24              :  */
      25              : #include "platform.h"
      26              : #include "taler-merchant-httpd_post-private-tokenfamilies.h"
      27              : #include "taler-merchant-httpd_helper.h"
      28              : #include <gnunet/gnunet_time_lib.h>
      29              : #include <taler/taler_json_lib.h>
      30              : #include "merchant-database/insert_token_family.h"
      31              : #include "merchant-database/get_token_family.h"
      32              : #include "merchant-database/preflight.h"
      33              : #include "merchant-database/start.h"
      34              : 
      35              : 
      36              : /**
      37              :  * How often do we retry the simple INSERT database transaction?
      38              :  */
      39              : #define MAX_RETRIES 3
      40              : 
      41              : 
      42              : /**
      43              :  * Check if the two token families are identical.
      44              :  *
      45              :  * @param tf1 token family to compare
      46              :  * @param tf2 other token family to compare
      47              :  * @return true if they are 'equal', false if not
      48              :  */
      49              : static bool
      50            0 : token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1,
      51              :                       const struct TALER_MERCHANTDB_TokenFamilyDetails *tf2)
      52              : {
      53              :   /* Note: we're not comparing 'cipher', as that is selected
      54              :      in the database to some default value and we currently
      55              :      do not allow the SPA to change it. As a result, it should
      56              :      always be "NULL" in tf1 and the DB-default in tf2. */
      57            0 :   return ( (0 == strcmp (tf1->slug,
      58            0 :                          tf2->slug)) &&
      59            0 :            (0 == strcmp (tf1->name,
      60            0 :                          tf2->name)) &&
      61            0 :            (0 == strcmp (tf1->description,
      62            0 :                          tf2->description)) &&
      63            0 :            (1 == json_equal (tf1->description_i18n,
      64            0 :                              tf2->description_i18n)) &&
      65            0 :            ( (tf1->extra_data == tf2->extra_data) ||
      66            0 :              (1 == json_equal (tf1->extra_data,
      67            0 :                                tf2->extra_data)) ) &&
      68            0 :            (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
      69              :                                        ==,
      70            0 :                                        tf2->valid_after)) &&
      71            0 :            (GNUNET_TIME_timestamp_cmp (tf1->valid_before,
      72              :                                        ==,
      73            0 :                                        tf2->valid_before)) &&
      74            0 :            (GNUNET_TIME_relative_cmp (tf1->duration,
      75              :                                       ==,
      76            0 :                                       tf2->duration)) &&
      77            0 :            (GNUNET_TIME_relative_cmp (tf1->validity_granularity,
      78              :                                       ==,
      79            0 :                                       tf2->validity_granularity)) &&
      80            0 :            (GNUNET_TIME_relative_cmp (tf1->start_offset,
      81              :                                       ==,
      82            0 :                                       tf2->start_offset)) &&
      83            0 :            (tf1->kind == tf2->kind) );
      84              : }
      85              : 
      86              : 
      87              : enum MHD_Result
      88            6 : TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
      89              :                                  struct MHD_Connection *connection,
      90              :                                  struct TMH_HandlerContext *hc)
      91              : {
      92            6 :   struct TMH_MerchantInstance *mi = hc->instance;
      93            6 :   struct TALER_MERCHANTDB_TokenFamilyDetails details = {
      94              :     .valid_before = GNUNET_TIME_UNIT_FOREVER_TS
      95              :   };
      96            6 :   const char *kind = NULL;
      97            6 :   bool no_valid_after = false;
      98              :   enum GNUNET_DB_QueryStatus qs;
      99              :   struct GNUNET_JSON_Specification spec[] = {
     100            6 :     TALER_JSON_spec_slug ("slug",
     101              :                           (const char **) &details.slug),
     102            6 :     GNUNET_JSON_spec_string ("name",
     103              :                              (const char **) &details.name),
     104            6 :     GNUNET_JSON_spec_string ("description",
     105              :                              (const char **) &details.description),
     106            6 :     GNUNET_JSON_spec_mark_optional (
     107              :       GNUNET_JSON_spec_json ("description_i18n",
     108              :                              &details.description_i18n),
     109              :       NULL),
     110            6 :     GNUNET_JSON_spec_mark_optional (
     111              :       GNUNET_JSON_spec_json ("extra_data",
     112              :                              &details.extra_data),
     113              :       NULL),
     114            6 :     GNUNET_JSON_spec_mark_optional (
     115              :       GNUNET_JSON_spec_timestamp ("valid_after",
     116              :                                   &details.valid_after),
     117              :       &no_valid_after),
     118            6 :     GNUNET_JSON_spec_mark_optional (
     119              :       GNUNET_JSON_spec_timestamp ("valid_before",
     120              :                                   &details.valid_before),
     121              :       NULL),
     122            6 :     GNUNET_JSON_spec_relative_time ("duration",
     123              :                                     &details.duration),
     124            6 :     GNUNET_JSON_spec_relative_time ("validity_granularity",
     125              :                                     &details.validity_granularity),
     126            6 :     GNUNET_JSON_spec_mark_optional (
     127              :       GNUNET_JSON_spec_relative_time ("start_offset",
     128              :                                       &details.start_offset),
     129              :       NULL),
     130            6 :     GNUNET_JSON_spec_string ("kind",
     131              :                              &kind),
     132            6 :     GNUNET_JSON_spec_end ()
     133              :   };
     134              :   struct GNUNET_TIME_Timestamp now
     135            6 :     = GNUNET_TIME_timestamp_get ();
     136              : 
     137            6 :   GNUNET_assert (NULL != mi);
     138              :   {
     139              :     enum GNUNET_GenericReturnValue res;
     140              : 
     141            6 :     res = TALER_MHD_parse_json_data (connection,
     142            6 :                                      hc->request_body,
     143              :                                      spec);
     144            6 :     if (GNUNET_OK != res)
     145              :     {
     146            0 :       GNUNET_break_op (0);
     147              :       return (GNUNET_NO == res)
     148              :              ? MHD_YES
     149            0 :              : MHD_NO;
     150              :     }
     151              :   }
     152            6 :   if (no_valid_after)
     153            2 :     details.valid_after = now;
     154              : 
     155              :   /* Ensure that valid_after is before valid_before */
     156            6 :   if (GNUNET_TIME_timestamp_cmp (details.valid_after,
     157              :                                  >=,
     158              :                                  details.valid_before))
     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              :                                        "valid_after >= valid_before");
     166              :   }
     167              : 
     168              :   /* Ensure that duration exceeds rounding plus start_offset */
     169            6 :   if (GNUNET_TIME_relative_cmp (details.duration,
     170              :                                 <,
     171              :                                 GNUNET_TIME_relative_add (details.
     172              :                                                           validity_granularity,
     173              :                                                           details.start_offset))
     174              :       )
     175              :   {
     176            0 :     GNUNET_break_op (0);
     177            0 :     GNUNET_JSON_parse_free (spec);
     178            0 :     return TALER_MHD_reply_with_error (connection,
     179              :                                        MHD_HTTP_BAD_REQUEST,
     180              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     181              :                                        "duration below validity_granularity plus start_offset");
     182              :   }
     183              : 
     184            6 :   if (0 ==
     185            6 :       strcmp (kind,
     186              :               "discount"))
     187            1 :     details.kind = TALER_MERCHANTDB_TFK_Discount;
     188            5 :   else if (0 ==
     189            5 :            strcmp (kind,
     190              :                    "subscription"))
     191            5 :     details.kind = TALER_MERCHANTDB_TFK_Subscription;
     192              :   else
     193              :   {
     194            0 :     GNUNET_break_op (0);
     195            0 :     GNUNET_JSON_parse_free (spec);
     196            0 :     return TALER_MHD_reply_with_error (connection,
     197              :                                        MHD_HTTP_BAD_REQUEST,
     198              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     199              :                                        "kind");
     200              :   }
     201              : 
     202            6 :   if (NULL == details.description_i18n)
     203            4 :     details.description_i18n = json_object ();
     204              : 
     205            6 :   if (! TALER_JSON_check_i18n (details.description_i18n))
     206              :   {
     207            0 :     GNUNET_break_op (0);
     208            0 :     GNUNET_JSON_parse_free (spec);
     209            0 :     return TALER_MHD_reply_with_error (connection,
     210              :                                        MHD_HTTP_BAD_REQUEST,
     211              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     212              :                                        "description_i18n");
     213              :   }
     214              : 
     215            6 :   if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS,
     216              :                                 !=,
     217            6 :                                 details.validity_granularity) &&
     218            6 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_relative_multiply (
     219              :                                   GNUNET_TIME_UNIT_DAYS,
     220              :                                   90),
     221              :                                 !=,
     222            6 :                                 details.validity_granularity) &&
     223            6 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS,
     224              :                                 !=,
     225            0 :                                 details.validity_granularity) &&
     226            0 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_WEEKS,
     227              :                                 !=,
     228            0 :                                 details.validity_granularity) &&
     229            0 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS,
     230              :                                 !=,
     231            0 :                                 details.validity_granularity) &&
     232            0 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS,
     233              :                                 !=,
     234            0 :                                 details.validity_granularity) &&
     235            0 :       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES,
     236              :                                 !=,
     237              :                                 details.validity_granularity)
     238              :       )
     239              :   {
     240            0 :     GNUNET_break_op (0);
     241            0 :     GNUNET_JSON_parse_free (spec);
     242            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     243              :                 "Received invalid validity_granularity value: %s\n",
     244              :                 GNUNET_STRINGS_relative_time_to_string (details.
     245              :                                                         validity_granularity,
     246              :                                                         false));
     247            0 :     return TALER_MHD_reply_with_error (connection,
     248              :                                        MHD_HTTP_BAD_REQUEST,
     249              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     250              :                                        "validity_granularity");
     251              :   }
     252              : 
     253              :   /* finally, interact with DB until no serialization error */
     254            6 :   for (unsigned int i = 0; i<MAX_RETRIES; i++)
     255              :   {
     256              :     /* Test if a token family of this id is known */
     257              :     struct TALER_MERCHANTDB_TokenFamilyDetails existing;
     258              : 
     259            6 :     TALER_MERCHANTDB_preflight (TMH_db);
     260            6 :     if (GNUNET_OK !=
     261            6 :         TALER_MERCHANTDB_start (TMH_db,
     262              :                                 "/post tokenfamilies"))
     263              :     {
     264            0 :       GNUNET_break (0);
     265            0 :       GNUNET_JSON_parse_free (spec);
     266            0 :       return TALER_MHD_reply_with_error (connection,
     267              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     268              :                                          TALER_EC_GENERIC_DB_START_FAILED,
     269              :                                          NULL);
     270              :     }
     271            6 :     qs = TALER_MERCHANTDB_insert_token_family (TMH_db,
     272            6 :                                                mi->settings.id,
     273            6 :                                                details.slug,
     274              :                                                &details);
     275            6 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     276              :                 "insert_token_family returned %d\n",
     277              :                 (int) qs);
     278            6 :     switch (qs)
     279              :     {
     280            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     281            0 :       GNUNET_break (0);
     282            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     283            0 :       GNUNET_JSON_parse_free (spec);
     284            0 :       return TALER_MHD_reply_with_error (
     285              :         connection,
     286              :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     287              :         TALER_EC_GENERIC_DB_STORE_FAILED,
     288              :         "insert_token_family");
     289            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     290            0 :       GNUNET_break (0);
     291            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     292            0 :       break;
     293            0 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     294            0 :       qs = TALER_MERCHANTDB_get_token_family (TMH_db,
     295            0 :                                               mi->settings.id,
     296            0 :                                               details.slug,
     297              :                                               &existing);
     298            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     299              :                   "get_token_family returned %d\n",
     300              :                   (int) qs);
     301            0 :       switch (qs)
     302              :       {
     303            0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     304              :         /* Clean up and fail hard */
     305            0 :         GNUNET_break (0);
     306            0 :         TALER_MERCHANTDB_rollback (TMH_db);
     307            0 :         GNUNET_JSON_parse_free (spec);
     308            0 :         return TALER_MHD_reply_with_error (
     309              :           connection,
     310              :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     311              :           TALER_EC_GENERIC_DB_FETCH_FAILED,
     312              :           "get_token_family");
     313            0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     314            0 :         TALER_MERCHANTDB_rollback (TMH_db);
     315            0 :         break;
     316            0 :       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     317            0 :         return TALER_MHD_reply_with_error (
     318              :           connection,
     319              :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     320              :           TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
     321              :           "get_token_family failed after insert_token_family failed");
     322            0 :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     323              :         {
     324              :           bool eq;
     325              : 
     326            0 :           eq = token_families_equal (&details,
     327              :                                      &existing);
     328            0 :           TALER_MERCHANTDB_token_family_details_free (&existing);
     329            0 :           TALER_MERCHANTDB_rollback (TMH_db);
     330            0 :           GNUNET_JSON_parse_free (spec);
     331              :           return eq
     332            0 :           ? TALER_MHD_reply_static (
     333              :             connection,
     334              :             MHD_HTTP_NO_CONTENT,
     335              :             NULL,
     336              :             NULL,
     337              :             0)
     338            0 :           : TALER_MHD_reply_with_error (
     339              :             connection,
     340              :             MHD_HTTP_CONFLICT,
     341              :             TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
     342            0 :             details.slug);
     343              :         }
     344              :       }
     345            0 :       break;
     346            6 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     347            6 :       qs = TALER_MERCHANTDB_commit (TMH_db);
     348            6 :       switch (qs)
     349              :       {
     350            0 :       case GNUNET_DB_STATUS_HARD_ERROR:
     351              :         /* Clean up and fail hard */
     352            0 :         GNUNET_break (0);
     353            0 :         TALER_MERCHANTDB_rollback (TMH_db);
     354            0 :         GNUNET_JSON_parse_free (spec);
     355            0 :         return TALER_MHD_reply_with_error (
     356              :           connection,
     357              :           MHD_HTTP_INTERNAL_SERVER_ERROR,
     358              :           TALER_EC_GENERIC_DB_COMMIT_FAILED,
     359              :           "insert_token_family");
     360            0 :       case GNUNET_DB_STATUS_SOFT_ERROR:
     361            0 :         TALER_MERCHANTDB_rollback (TMH_db);
     362            0 :         break;
     363            6 :       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     364              :       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     365            6 :         break;
     366              :       }
     367            6 :       break;
     368              :     }
     369            6 :     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     370            6 :       break;
     371              :   } /* for(i... MAX_RETRIES) */
     372              : 
     373            6 :   GNUNET_JSON_parse_free (spec);
     374            6 :   if (qs < 0)
     375              :   {
     376            0 :     GNUNET_break (0);
     377            0 :     return TALER_MHD_reply_with_error (
     378              :       connection,
     379              :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     380              :       TALER_EC_GENERIC_DB_SOFT_FAILURE,
     381              :       NULL);
     382              :   }
     383            6 :   return TALER_MHD_reply_static (connection,
     384              :                                  MHD_HTTP_NO_CONTENT,
     385              :                                  NULL,
     386              :                                  NULL,
     387              :                                  0);
     388              : }
     389              : 
     390              : 
     391              : /* end of taler-merchant-httpd_post-private-tokenfamilies.c */
        

Generated by: LCOV version 2.0-1