LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-private-webhooks.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 72.8 % 81 59
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 2 2

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2022 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-webhooks.c
      22              :  * @brief implementing POST /webhooks request handling
      23              :  * @author Priscilla HUANG
      24              :  */
      25              : #include "platform.h"
      26              : #include "taler-merchant-httpd_post-private-webhooks.h"
      27              : #include "taler-merchant-httpd_helper.h"
      28              : #include <taler/taler_json_lib.h>
      29              : #include "merchant-database/insert_webhook.h"
      30              : #include "merchant-database/get_webhook.h"
      31              : #include "merchant-database/start.h"
      32              : 
      33              : /**
      34              :  * How often do we retry the simple INSERT database transaction?
      35              :  */
      36              : #define MAX_RETRIES 3
      37              : 
      38              : 
      39              : /**
      40              :  * Check if the two webhooks are identical.
      41              :  *
      42              :  * @param w1 webhook to compare
      43              :  * @param w2 other webhook to compare
      44              :  * @return true if they are 'equal', false if not or of payto_uris is not an array
      45              :  */
      46              : static bool
      47            4 : webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *w1,
      48              :                 const struct TALER_MERCHANTDB_WebhookDetails *w2)
      49              : {
      50            4 :   return ( (0 == strcmp (w1->event_type,
      51            6 :                          w2->event_type)) &&
      52            2 :            (0 == strcmp (w1->url,
      53            2 :                          w2->url)) &&
      54            2 :            (0 == strcmp (w1->http_method,
      55            2 :                          w2->http_method)) &&
      56            2 :            ( ( (NULL == w1->header_template) &&
      57            0 :                (NULL == w2->header_template) ) ||
      58            2 :              ( (NULL != w1->header_template) &&
      59            2 :                (NULL != w2->header_template) &&
      60            2 :                (0 == strcmp (w1->header_template,
      61            8 :                              w2->header_template)) ) ) &&
      62            2 :            ( ( (NULL == w1->body_template) &&
      63            0 :                (NULL == w2->body_template) ) ||
      64            2 :              ( (NULL != w1->body_template) &&
      65            2 :                (NULL != w2->body_template) &&
      66            2 :                (0 == strcmp (w1->body_template,
      67            2 :                              w2->body_template)) )  ) );
      68              : }
      69              : 
      70              : 
      71              : enum MHD_Result
      72           18 : TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
      73              :                            struct MHD_Connection *connection,
      74              :                            struct TMH_HandlerContext *hc)
      75              : {
      76           18 :   struct TMH_MerchantInstance *mi = hc->instance;
      77           18 :   struct TALER_MERCHANTDB_WebhookDetails wb = { 0 };
      78              :   const char *webhook_id;
      79              :   enum GNUNET_DB_QueryStatus qs;
      80              :   struct GNUNET_JSON_Specification spec[] = {
      81           18 :     TALER_JSON_spec_slug ("webhook_id",
      82              :                           &webhook_id),
      83           18 :     GNUNET_JSON_spec_string ("event_type",
      84              :                              (const char **) &wb.event_type),
      85           18 :     TALER_JSON_spec_web_url ("url",
      86              :                              (const char **) &wb.url),
      87           18 :     GNUNET_JSON_spec_string ("http_method",
      88              :                              (const char **) &wb.http_method),
      89           18 :     GNUNET_JSON_spec_mark_optional (
      90              :       GNUNET_JSON_spec_string ("header_template",
      91              :                                (const char **) &wb.header_template),
      92              :       NULL),
      93           18 :     GNUNET_JSON_spec_mark_optional (
      94              :       GNUNET_JSON_spec_string ("body_template",
      95              :                                (const char **) &wb.body_template),
      96              :       NULL),
      97           18 :     GNUNET_JSON_spec_end ()
      98              :   };
      99              : 
     100           18 :   GNUNET_assert (NULL != mi);
     101              :   {
     102              :     enum GNUNET_GenericReturnValue res;
     103              : 
     104           18 :     res = TALER_MHD_parse_json_data (connection,
     105           18 :                                      hc->request_body,
     106              :                                      spec);
     107           18 :     if (GNUNET_OK != res)
     108              :     {
     109            0 :       GNUNET_break_op (0);
     110              :       return (GNUNET_NO == res)
     111              :              ? MHD_YES
     112            0 :              : MHD_NO;
     113              :     }
     114              :   }
     115              : 
     116              : 
     117              :   /* finally, interact with DB until no serialization error */
     118           18 :   for (unsigned int i = 0; i<MAX_RETRIES; i++)
     119              :   {
     120              :     /* Test if a webhook of this id is known */
     121              :     struct TALER_MERCHANTDB_WebhookDetails ewb;
     122              : 
     123           18 :     if (GNUNET_OK !=
     124           18 :         TALER_MERCHANTDB_start (TMH_db,
     125              :                                 "/post webhooks"))
     126              :     {
     127            0 :       GNUNET_break (0);
     128            0 :       GNUNET_JSON_parse_free (spec);
     129            4 :       return TALER_MHD_reply_with_error (connection,
     130              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     131              :                                          TALER_EC_GENERIC_DB_START_FAILED,
     132              :                                          NULL);
     133              :     }
     134           18 :     qs = TALER_MERCHANTDB_get_webhook (TMH_db,
     135           18 :                                        mi->settings.id,
     136              :                                        webhook_id,
     137              :                                        &ewb);
     138           18 :     switch (qs)
     139              :     {
     140            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     141              :       /* Clean up and fail hard */
     142            0 :       GNUNET_break (0);
     143            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     144            0 :       GNUNET_JSON_parse_free (spec);
     145            0 :       return TALER_MHD_reply_with_error (connection,
     146              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     147              :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     148              :                                          NULL);
     149            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     150              :       /* restart transaction */
     151            0 :       goto retry;
     152           14 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     153              :       /* Good, we can proceed! */
     154           14 :       break;
     155            4 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     156              :       /* idempotency check: is ewb == wb? */
     157              :       {
     158              :         bool eq;
     159              : 
     160            4 :         eq = webhooks_equal (&wb,
     161              :                              &ewb);
     162            4 :         TALER_MERCHANTDB_webhook_details_free (&ewb);
     163            4 :         TALER_MERCHANTDB_rollback (TMH_db);
     164            4 :         GNUNET_JSON_parse_free (spec);
     165              :         return eq
     166            2 :           ? TALER_MHD_reply_static (connection,
     167              :                                     MHD_HTTP_NO_CONTENT,
     168              :                                     NULL,
     169              :                                     NULL,
     170              :                                     0)
     171            6 :           : TALER_MHD_reply_with_error (connection,
     172              :                                         MHD_HTTP_CONFLICT,
     173              :                                         TALER_EC_MERCHANT_PRIVATE_POST_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS,
     174              :                                         webhook_id);
     175              :       }
     176              :     } /* end switch (qs) */
     177              : 
     178           14 :     qs = TALER_MERCHANTDB_insert_webhook (TMH_db,
     179           14 :                                           mi->settings.id,
     180              :                                           webhook_id,
     181              :                                           &wb);
     182           14 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     183              :     {
     184            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     185           14 :       break;
     186              :     }
     187           14 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     188              :     {
     189              :       /* A concurrent request created a webhook with the same ID
     190              :          between our lookup above and this INSERT. */
     191            0 :       TALER_MERCHANTDB_rollback (TMH_db);
     192            0 :       GNUNET_JSON_parse_free (spec);
     193            0 :       return TALER_MHD_reply_with_error (
     194              :         connection,
     195              :         MHD_HTTP_CONFLICT,
     196              :         TALER_EC_MERCHANT_PRIVATE_POST_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS,
     197              :         webhook_id);
     198              :     }
     199           14 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     200              :     {
     201           14 :       qs = TALER_MERCHANTDB_commit (TMH_db);
     202           14 :       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     203           14 :         break;
     204              :     }
     205            0 : retry:
     206            0 :     GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     207            0 :     TALER_MERCHANTDB_rollback (TMH_db);
     208              :   } /* for RETRIES loop */
     209           14 :   GNUNET_JSON_parse_free (spec);
     210           14 :   if (qs < 0)
     211              :   {
     212            0 :     GNUNET_break (0);
     213            0 :     return TALER_MHD_reply_with_error (
     214              :       connection,
     215              :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     216              :       (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     217              :       ? TALER_EC_GENERIC_DB_SOFT_FAILURE
     218              :       : TALER_EC_GENERIC_DB_COMMIT_FAILED,
     219              :       NULL);
     220              :   }
     221           14 :   return TALER_MHD_reply_static (connection,
     222              :                                  MHD_HTTP_NO_CONTENT,
     223              :                                  NULL,
     224              :                                  NULL,
     225              :                                  0);
     226              : }
     227              : 
     228              : 
     229              : /* end of taler-merchant-httpd_post-private-webhooks.c */
        

Generated by: LCOV version 2.0-1