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

Generated by: LCOV version 2.0-1