merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-private-tokenfamilies.c (14603B)


      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 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   return ( (0 == strcmp (tf1->slug,
     58                          tf2->slug)) &&
     59            (0 == strcmp (tf1->name,
     60                          tf2->name)) &&
     61            (0 == strcmp (tf1->description,
     62                          tf2->description)) &&
     63            (1 == json_equal (tf1->description_i18n,
     64                              tf2->description_i18n)) &&
     65            ( (tf1->extra_data == tf2->extra_data) ||
     66              (1 == json_equal (tf1->extra_data,
     67                                tf2->extra_data)) ) &&
     68            (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
     69                                        ==,
     70                                        tf2->valid_after)) &&
     71            (GNUNET_TIME_timestamp_cmp (tf1->valid_before,
     72                                        ==,
     73                                        tf2->valid_before)) &&
     74            (GNUNET_TIME_relative_cmp (tf1->duration,
     75                                       ==,
     76                                       tf2->duration)) &&
     77            (GNUNET_TIME_relative_cmp (tf1->validity_granularity,
     78                                       ==,
     79                                       tf2->validity_granularity)) &&
     80            (GNUNET_TIME_relative_cmp (tf1->start_offset,
     81                                       ==,
     82                                       tf2->start_offset)) &&
     83            (tf1->kind == tf2->kind) );
     84 }
     85 
     86 
     87 enum MHD_Result
     88 TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
     89                                  struct MHD_Connection *connection,
     90                                  struct TMH_HandlerContext *hc)
     91 {
     92   struct TMH_MerchantInstance *mi = hc->instance;
     93   struct TALER_MERCHANTDB_TokenFamilyDetails details = {
     94     .valid_before = GNUNET_TIME_UNIT_FOREVER_TS
     95   };
     96   const char *kind = NULL;
     97   bool no_valid_after = false;
     98   enum GNUNET_DB_QueryStatus qs;
     99   struct GNUNET_JSON_Specification spec[] = {
    100     TALER_JSON_spec_slug ("slug",
    101                           (const char **) &details.slug),
    102     GNUNET_JSON_spec_string ("name",
    103                              (const char **) &details.name),
    104     GNUNET_JSON_spec_string ("description",
    105                              (const char **) &details.description),
    106     GNUNET_JSON_spec_mark_optional (
    107       GNUNET_JSON_spec_json ("description_i18n",
    108                              &details.description_i18n),
    109       NULL),
    110     GNUNET_JSON_spec_mark_optional (
    111       GNUNET_JSON_spec_json ("extra_data",
    112                              &details.extra_data),
    113       NULL),
    114     GNUNET_JSON_spec_mark_optional (
    115       GNUNET_JSON_spec_timestamp ("valid_after",
    116                                   &details.valid_after),
    117       &no_valid_after),
    118     GNUNET_JSON_spec_mark_optional (
    119       GNUNET_JSON_spec_timestamp ("valid_before",
    120                                   &details.valid_before),
    121       NULL),
    122     GNUNET_JSON_spec_relative_time ("duration",
    123                                     &details.duration),
    124     GNUNET_JSON_spec_relative_time ("validity_granularity",
    125                                     &details.validity_granularity),
    126     GNUNET_JSON_spec_mark_optional (
    127       GNUNET_JSON_spec_relative_time ("start_offset",
    128                                       &details.start_offset),
    129       NULL),
    130     GNUNET_JSON_spec_string ("kind",
    131                              &kind),
    132     GNUNET_JSON_spec_end ()
    133   };
    134   struct GNUNET_TIME_Timestamp now
    135     = GNUNET_TIME_timestamp_get ();
    136 
    137   GNUNET_assert (NULL != mi);
    138   {
    139     enum GNUNET_GenericReturnValue res;
    140 
    141     res = TALER_MHD_parse_json_data (connection,
    142                                      hc->request_body,
    143                                      spec);
    144     if (GNUNET_OK != res)
    145     {
    146       GNUNET_break_op (0);
    147       return (GNUNET_NO == res)
    148              ? MHD_YES
    149              : MHD_NO;
    150     }
    151   }
    152   if (no_valid_after)
    153     details.valid_after = now;
    154 
    155   /* Ensure that valid_after is before valid_before */
    156   if (GNUNET_TIME_timestamp_cmp (details.valid_after,
    157                                  >=,
    158                                  details.valid_before))
    159   {
    160     GNUNET_break_op (0);
    161     GNUNET_JSON_parse_free (spec);
    162     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   if (GNUNET_TIME_relative_cmp (details.duration,
    170                                 <,
    171                                 GNUNET_TIME_relative_add (details.
    172                                                           validity_granularity,
    173                                                           details.start_offset))
    174       )
    175   {
    176     GNUNET_break_op (0);
    177     GNUNET_JSON_parse_free (spec);
    178     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   if (0 ==
    185       strcmp (kind,
    186               "discount"))
    187     details.kind = TALER_MERCHANTDB_TFK_Discount;
    188   else if (0 ==
    189            strcmp (kind,
    190                    "subscription"))
    191     details.kind = TALER_MERCHANTDB_TFK_Subscription;
    192   else
    193   {
    194     GNUNET_break_op (0);
    195     GNUNET_JSON_parse_free (spec);
    196     return TALER_MHD_reply_with_error (connection,
    197                                        MHD_HTTP_BAD_REQUEST,
    198                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    199                                        "kind");
    200   }
    201 
    202   if (NULL == details.description_i18n)
    203     details.description_i18n = json_object ();
    204 
    205   if (! TALER_JSON_check_i18n (details.description_i18n))
    206   {
    207     GNUNET_break_op (0);
    208     GNUNET_JSON_parse_free (spec);
    209     return TALER_MHD_reply_with_error (connection,
    210                                        MHD_HTTP_BAD_REQUEST,
    211                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    212                                        "description_i18n");
    213   }
    214 
    215   if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS,
    216                                 !=,
    217                                 details.validity_granularity) &&
    218       GNUNET_TIME_relative_cmp (GNUNET_TIME_relative_multiply (
    219                                   GNUNET_TIME_UNIT_DAYS,
    220                                   90),
    221                                 !=,
    222                                 details.validity_granularity) &&
    223       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS,
    224                                 !=,
    225                                 details.validity_granularity) &&
    226       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_WEEKS,
    227                                 !=,
    228                                 details.validity_granularity) &&
    229       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS,
    230                                 !=,
    231                                 details.validity_granularity) &&
    232       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS,
    233                                 !=,
    234                                 details.validity_granularity) &&
    235       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES,
    236                                 !=,
    237                                 details.validity_granularity)
    238       )
    239   {
    240     GNUNET_break_op (0);
    241     GNUNET_JSON_parse_free (spec);
    242     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     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   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     TALER_MERCHANTDB_preflight (TMH_db);
    260     if (GNUNET_OK !=
    261         TALER_MERCHANTDB_start (TMH_db,
    262                                 "/post tokenfamilies"))
    263     {
    264       GNUNET_break (0);
    265       GNUNET_JSON_parse_free (spec);
    266       return TALER_MHD_reply_with_error (connection,
    267                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    268                                          TALER_EC_GENERIC_DB_START_FAILED,
    269                                          NULL);
    270     }
    271     qs = TALER_MERCHANTDB_insert_token_family (TMH_db,
    272                                                mi->settings.id,
    273                                                details.slug,
    274                                                &details);
    275     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    276                 "insert_token_family returned %d\n",
    277                 (int) qs);
    278     switch (qs)
    279     {
    280     case GNUNET_DB_STATUS_HARD_ERROR:
    281       GNUNET_break (0);
    282       TALER_MERCHANTDB_rollback (TMH_db);
    283       GNUNET_JSON_parse_free (spec);
    284       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     case GNUNET_DB_STATUS_SOFT_ERROR:
    290       GNUNET_break (0);
    291       TALER_MERCHANTDB_rollback (TMH_db);
    292       break;
    293     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    294       qs = TALER_MERCHANTDB_get_token_family (TMH_db,
    295                                               mi->settings.id,
    296                                               details.slug,
    297                                               &existing);
    298       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    299                   "get_token_family returned %d\n",
    300                   (int) qs);
    301       switch (qs)
    302       {
    303       case GNUNET_DB_STATUS_HARD_ERROR:
    304         /* Clean up and fail hard */
    305         GNUNET_break (0);
    306         TALER_MERCHANTDB_rollback (TMH_db);
    307         GNUNET_JSON_parse_free (spec);
    308         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       case GNUNET_DB_STATUS_SOFT_ERROR:
    314         TALER_MERCHANTDB_rollback (TMH_db);
    315         break;
    316       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    317         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       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    323         {
    324           bool eq;
    325 
    326           eq = token_families_equal (&details,
    327                                      &existing);
    328           TALER_MERCHANTDB_token_family_details_free (&existing);
    329           TALER_MERCHANTDB_rollback (TMH_db);
    330           GNUNET_JSON_parse_free (spec);
    331           return eq
    332           ? TALER_MHD_reply_static (
    333             connection,
    334             MHD_HTTP_NO_CONTENT,
    335             NULL,
    336             NULL,
    337             0)
    338           : TALER_MHD_reply_with_error (
    339             connection,
    340             MHD_HTTP_CONFLICT,
    341             TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
    342             details.slug);
    343         }
    344       }
    345       break;
    346     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    347       qs = TALER_MERCHANTDB_commit (TMH_db);
    348       switch (qs)
    349       {
    350       case GNUNET_DB_STATUS_HARD_ERROR:
    351         /* Clean up and fail hard */
    352         GNUNET_break (0);
    353         TALER_MERCHANTDB_rollback (TMH_db);
    354         GNUNET_JSON_parse_free (spec);
    355         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       case GNUNET_DB_STATUS_SOFT_ERROR:
    361         TALER_MERCHANTDB_rollback (TMH_db);
    362         break;
    363       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    364       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    365         break;
    366       }
    367       break;
    368     }
    369     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    370       break;
    371   } /* for(i... MAX_RETRIES) */
    372 
    373   GNUNET_JSON_parse_free (spec);
    374   if (qs < 0)
    375   {
    376     GNUNET_break (0);
    377     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   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 */