LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_extensions.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 58 151 38.4 %
Date: 2025-07-09 07:38:29 Functions: 3 5 60.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2021, 2023 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             :   You should have received a copy of the GNU Affero General Public License along with
      13             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      14             : */
      15             : /**
      16             :  * @file taler-exchange-httpd_extensions.c
      17             :  * @brief Handle extensions (age-restriction, policy extensions)
      18             :  * @author Özgür Kesim
      19             :  */
      20             : #include "taler/platform.h"
      21             : #include <gnunet/gnunet_json_lib.h>
      22             : #include "taler/taler_dbevents.h"
      23             : #include "taler-exchange-httpd_keys.h"
      24             : #include "taler-exchange-httpd_responses.h"
      25             : #include "taler-exchange-httpd_extensions.h"
      26             : #include "taler/taler_extensions_policy.h"
      27             : #include "taler/taler_json_lib.h"
      28             : #include "taler/taler_mhd_lib.h"
      29             : #include "taler/taler_extensions.h"
      30             : #include <jansson.h>
      31             : 
      32             : /**
      33             :  * Handler listening for extensions updates by other exchange
      34             :  * services.
      35             :  */
      36             : static struct GNUNET_DB_EventHandler *extensions_eh;
      37             : 
      38             : /**
      39             :  * Function called whenever another exchange process has updated
      40             :  * the extensions data in the database.
      41             :  *
      42             :  * @param cls NULL
      43             :  * @param extra type of the extension
      44             :  * @param extra_size number of bytes in @a extra
      45             :  */
      46             : static void
      47          11 : extension_update_event_cb (void *cls,
      48             :                            const void *extra,
      49             :                            size_t extra_size)
      50             : {
      51             :   uint32_t nbo_type;
      52             :   enum TALER_Extension_Type type;
      53             :   const struct TALER_Extension *extension;
      54             : 
      55             :   (void) cls;
      56          11 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      57             :               "Received extensions update event\n");
      58             : 
      59          11 :   if (sizeof(nbo_type) != extra_size)
      60             :   {
      61           0 :     GNUNET_break (0);
      62           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      63             :                 "Oops, incorrect size of extra for TALER_Extension_type\n");
      64           0 :     return;
      65             :   }
      66             : 
      67          11 :   GNUNET_assert (NULL != extra);
      68             : 
      69          11 :   nbo_type = *(uint32_t *) extra;
      70          11 :   type = (enum TALER_Extension_Type) (int) ntohl (nbo_type);
      71             : 
      72             :   /* Get the corresponding extension */
      73          11 :   extension = TALER_extensions_get_by_type (type);
      74          11 :   if (NULL == extension)
      75             :   {
      76           0 :     GNUNET_break (0);
      77           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      78             :                 "Oops, unknown extension type: %d\n", type);
      79           0 :     return;
      80             :   }
      81             : 
      82             :   // Get the manifest from the database as string
      83             :   {
      84          11 :     char *manifest_str = NULL;
      85             :     enum GNUNET_DB_QueryStatus qs;
      86             :     json_error_t err;
      87             :     json_t *manifest_js;
      88             :     enum GNUNET_GenericReturnValue ret;
      89             : 
      90          11 :     qs = TEH_plugin->get_extension_manifest (TEH_plugin->cls,
      91          11 :                                              extension->name,
      92             :                                              &manifest_str);
      93             : 
      94          11 :     if (qs < 0)
      95             :     {
      96           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      97             :                   "Couldn't get extension manifest\n");
      98           0 :       GNUNET_break (0);
      99           0 :       return;
     100             :     }
     101             : 
     102             :     // No config found -> disable extension
     103          11 :     if (NULL == manifest_str)
     104             :     {
     105           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     106             :                   "No manifest found for extension %s, disabling it\n",
     107             :                   extension->name);
     108           0 :       extension->disable ((struct TALER_Extension *) extension);
     109           0 :       return;
     110             :     }
     111             : 
     112             :     // Parse the string as JSON
     113          11 :     manifest_js = json_loads (manifest_str, JSON_DECODE_ANY, &err);
     114          11 :     if (NULL == manifest_js)
     115             :     {
     116           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     117             :                   "Failed to parse manifest for extension `%s' as JSON: %s (%s)\n",
     118             :                   extension->name,
     119             :                   err.text,
     120             :                   err.source);
     121           0 :       GNUNET_break (0);
     122           0 :       free (manifest_str);
     123           0 :       return;
     124             :     }
     125             : 
     126             :     // Call the parser for the extension
     127          11 :     ret = extension->load_config (
     128          11 :       json_object_get (manifest_js, "config"),
     129             :       (struct TALER_Extension *) extension);
     130             : 
     131          11 :     if (GNUNET_OK != ret)
     132             :     {
     133           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     134             :                   "Couldn't parse configuration for extension %s from the manifest in the database: %s\n",
     135             :                   extension->name,
     136             :                   manifest_str);
     137           0 :       GNUNET_break (0);
     138             :     }
     139             : 
     140          11 :     free (manifest_str);
     141          11 :     json_decref (manifest_js);
     142             :   }
     143             : 
     144             :   /* Special case age restriction: Update global flag and mask  */
     145          11 :   if (TALER_Extension_AgeRestriction == type)
     146             :   {
     147             :     const struct TALER_AgeRestrictionConfig *conf =
     148          11 :       TALER_extensions_get_age_restriction_config ();
     149          11 :     TEH_age_restriction_enabled = false;
     150          11 :     if (NULL != conf)
     151             :     {
     152          11 :       TEH_age_restriction_enabled = extension->enabled;
     153          11 :       TEH_age_restriction_config = *conf;
     154          11 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     155             :                   "[age restriction] DB event has changed the config to %s with mask: %s\n",
     156             :                   TEH_age_restriction_enabled ? "enabled": "DISABLED",
     157             :                   TALER_age_mask_to_string (&conf->mask));
     158             :     }
     159             :   }
     160             : 
     161             :   // Finally, call TEH_keys_update_states in order to refresh the cached
     162             :   // values.
     163          11 :   TEH_keys_update_states ();
     164             : }
     165             : 
     166             : 
     167             : enum GNUNET_GenericReturnValue
     168          21 : TEH_extensions_init ()
     169             : {
     170             :   /* Set the event handler for updates */
     171          21 :   struct GNUNET_DB_EventHeaderP ev = {
     172          21 :     .size = htons (sizeof (ev)),
     173          21 :     .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
     174             :   };
     175             : 
     176             :   /* Load the shared libraries first */
     177          21 :   if (GNUNET_OK !=
     178          21 :       TALER_extensions_init (TEH_cfg))
     179             :   {
     180           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     181             :                 "failed to load extensions");
     182           0 :     return GNUNET_SYSERR;
     183             :   }
     184             : 
     185             :   /* Check for age restriction */
     186             :   {
     187             :     const struct TALER_AgeRestrictionConfig *arc;
     188             : 
     189          21 :     if (NULL !=
     190          21 :         (arc = TALER_extensions_get_age_restriction_config ()))
     191          11 :       TEH_age_restriction_config = *arc;
     192             :   }
     193             : 
     194          42 :   extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
     195          21 :                                             GNUNET_TIME_UNIT_FOREVER_REL,
     196             :                                             &ev,
     197             :                                             &extension_update_event_cb,
     198             :                                             NULL);
     199          21 :   if (NULL == extensions_eh)
     200             :   {
     201           0 :     GNUNET_break (0);
     202           0 :     return GNUNET_SYSERR;
     203             :   }
     204             : 
     205             :   /* Trigger the initial load of configuration from the db */
     206          21 :   for (const struct TALER_Extensions *it = TALER_extensions_get_head ();
     207          32 :        NULL != it && NULL != it->extension;
     208          11 :        it = it->next)
     209             :   {
     210          11 :     const struct TALER_Extension *ext = it->extension;
     211          11 :     uint32_t typ = htonl (ext->type);
     212             :     json_t *jmani;
     213             :     char *manifest;
     214             : 
     215          11 :     jmani = ext->manifest (ext);
     216          11 :     manifest = json_dumps (jmani,
     217             :                            JSON_COMPACT);
     218          11 :     json_decref (jmani);
     219          11 :     TEH_plugin->set_extension_manifest (TEH_plugin->cls,
     220          11 :                                         ext->name,
     221             :                                         manifest);
     222          11 :     free (manifest);
     223          11 :     extension_update_event_cb (NULL,
     224             :                                &typ,
     225             :                                sizeof(typ));
     226             :   }
     227             : 
     228          21 :   return GNUNET_OK;
     229             : }
     230             : 
     231             : 
     232             : void
     233          21 : TEH_extensions_done ()
     234             : {
     235          21 :   if (NULL != extensions_eh)
     236             :   {
     237          21 :     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
     238             :                                      extensions_eh);
     239          21 :     extensions_eh = NULL;
     240             :   }
     241          21 : }
     242             : 
     243             : 
     244             : /*
     245             :  * @brief Execute database transactions for /extensions/policy_* POST requests.
     246             :  *
     247             :  * @param cls a `struct TALER_PolicyFulfillmentOutcome`
     248             :  * @param connection MHD request context
     249             :  * @param[out] mhd_ret set to MHD status on error
     250             :  * @return transaction status
     251             :  */
     252             : static enum GNUNET_DB_QueryStatus
     253           0 : policy_fulfillment_transaction (
     254             :   void *cls,
     255             :   struct MHD_Connection *connection,
     256             :   MHD_RESULT *mhd_ret)
     257             : {
     258           0 :   struct TALER_PolicyFulfillmentTransactionData *fulfillment = cls;
     259             : 
     260             :   /* FIXME[oec]: use connection and mhd_ret? */
     261             :   (void) connection;
     262             :   (void) mhd_ret;
     263             : 
     264           0 :   return TEH_plugin->add_policy_fulfillment_proof (TEH_plugin->cls,
     265             :                                                    fulfillment);
     266             : }
     267             : 
     268             : 
     269             : /* FIXME[oec]-#7999: In this handler: do we transition correctly between states? */
     270             : MHD_RESULT
     271           0 : TEH_extensions_post_handler (
     272             :   struct TEH_RequestContext *rc,
     273             :   const json_t *root,
     274             :   const char *const args[])
     275             : {
     276           0 :   const struct TALER_Extension *ext = NULL;
     277             :   json_t *output;
     278           0 :   struct TALER_PolicyDetails *policy_details = NULL;
     279           0 :   size_t policy_details_count = 0;
     280             : 
     281             : 
     282           0 :   if (NULL == args[0])
     283             :   {
     284           0 :     GNUNET_break_op (0);
     285           0 :     return TALER_MHD_reply_with_error (rc->connection,
     286             :                                        MHD_HTTP_NOT_FOUND,
     287             :                                        TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     288             :                                        "/extensions/$EXTENSION");
     289             :   }
     290             : 
     291           0 :   ext = TALER_extensions_get_by_name (args[0]);
     292           0 :   if (NULL == ext)
     293             :   {
     294           0 :     GNUNET_break_op (0);
     295           0 :     return TALER_MHD_reply_with_error (rc->connection,
     296             :                                        MHD_HTTP_NOT_FOUND,
     297             :                                        TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     298             :                                        "/extensions/$EXTENSION unknown");
     299             :   }
     300             : 
     301           0 :   if (NULL == ext->policy_post_handler)
     302           0 :     return TALER_MHD_reply_with_error (rc->connection,
     303             :                                        MHD_HTTP_NOT_IMPLEMENTED,
     304             :                                        TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     305             :                                        "POST /extensions/$EXTENSION not supported");
     306             : 
     307             :   /*  Extract hash_codes and retrieve related policy_details from the DB */
     308             :   {
     309             :     enum GNUNET_GenericReturnValue ret;
     310             :     enum GNUNET_DB_QueryStatus qs;
     311             :     const char *error_msg;
     312             :     struct GNUNET_HashCode *hcs;
     313             :     size_t len;
     314             :     json_t*val;
     315             :     size_t idx;
     316           0 :     json_t *jhash_codes = json_object_get (root,
     317             :                                            "policy_hash_codes");
     318           0 :     if (! json_is_array (jhash_codes))
     319           0 :       return TALER_MHD_reply_with_error (rc->connection,
     320             :                                          MHD_HTTP_BAD_REQUEST,
     321             :                                          TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     322             :                                          "policy_hash_codes are missing");
     323             : 
     324           0 :     len = json_array_size (jhash_codes);
     325           0 :     hcs = GNUNET_new_array (len,
     326             :                             struct GNUNET_HashCode);
     327           0 :     policy_details = GNUNET_new_array (len,
     328             :                                        struct TALER_PolicyDetails);
     329           0 :     ret = GNUNET_OK;
     330           0 :     json_array_foreach (jhash_codes, idx, val)
     331             :     {
     332             :       struct GNUNET_JSON_Specification spec[] = {
     333           0 :         GNUNET_JSON_spec_fixed_auto (NULL, &hcs[idx]),
     334           0 :         GNUNET_JSON_spec_end ()
     335             :       };
     336             : 
     337           0 :       ret = GNUNET_JSON_parse (val,
     338             :                                spec,
     339             :                                &error_msg,
     340             :                                NULL);
     341           0 :       if (GNUNET_OK != ret)
     342           0 :         break;
     343             : 
     344           0 :       qs = TEH_plugin->get_policy_details (TEH_plugin->cls,
     345           0 :                                            &hcs[idx],
     346           0 :                                            &policy_details[idx]);
     347           0 :       if (0 > qs)
     348             :       {
     349           0 :         GNUNET_free (hcs);
     350           0 :         GNUNET_free (policy_details);
     351           0 :         return TALER_MHD_reply_with_error (rc->connection,
     352             :                                            MHD_HTTP_BAD_REQUEST,
     353             :                                            TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     354             :                                            "a policy_hash_code couldn't be found");
     355             :       }
     356             : 
     357             :       /* We proceed according to the state of fulfillment */
     358           0 :       switch (policy_details[idx].fulfillment_state)
     359             :       {
     360           0 :       case TALER_PolicyFulfillmentReady:
     361           0 :         break;
     362           0 :       case TALER_PolicyFulfillmentInsufficient:
     363           0 :         error_msg = "a policy is not yet fully funded";
     364           0 :         ret = GNUNET_SYSERR;
     365           0 :         break;
     366           0 :       case TALER_PolicyFulfillmentTimeout:
     367           0 :         error_msg = "a policy is has already timed out";
     368           0 :         ret = GNUNET_SYSERR;
     369           0 :         break;
     370           0 :       case TALER_PolicyFulfillmentSuccess:
     371             :         /* FIXME[oec]-#8001: Idempotency handling. */
     372           0 :         GNUNET_break (0);
     373           0 :         break;
     374           0 :       case TALER_PolicyFulfillmentFailure:
     375             :         /* FIXME[oec]-#7999: What to do in the failure case? */
     376           0 :         GNUNET_break (0);
     377           0 :         break;
     378           0 :       default:
     379             :         /* Unknown state */
     380           0 :         GNUNET_assert (0);
     381             :       }
     382             : 
     383           0 :       if (GNUNET_OK != ret)
     384           0 :         break;
     385             :     }
     386             : 
     387           0 :     GNUNET_free (hcs);
     388             : 
     389           0 :     if (GNUNET_OK != ret)
     390             :     {
     391           0 :       GNUNET_free (policy_details);
     392           0 :       return TALER_MHD_reply_with_error (rc->connection,
     393             :                                          MHD_HTTP_BAD_REQUEST,
     394             :                                          TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     395             :                                          error_msg);
     396             :     }
     397             :   }
     398             : 
     399             : 
     400           0 :   if (GNUNET_OK !=
     401           0 :       ext->policy_post_handler (root,
     402             :                                 &args[1],
     403             :                                 policy_details,
     404             :                                 policy_details_count,
     405             :                                 &output))
     406             :   {
     407           0 :     return TALER_MHD_reply_json_steal (
     408             :       rc->connection,
     409             :       output,
     410             :       MHD_HTTP_BAD_REQUEST);
     411             :   }
     412             : 
     413             :   /* execute fulfillment transaction */
     414             :   {
     415             :     MHD_RESULT mhd_ret;
     416           0 :     struct TALER_PolicyFulfillmentTransactionData fulfillment = {
     417             :       .proof = root,
     418           0 :       .timestamp = GNUNET_TIME_timestamp_get (),
     419             :       .details = policy_details,
     420             :       .details_count = policy_details_count
     421             :     };
     422             : 
     423           0 :     if (GNUNET_OK !=
     424           0 :         TEH_DB_run_transaction (rc->connection,
     425             :                                 "execute policy fulfillment",
     426             :                                 TEH_MT_REQUEST_POLICY_FULFILLMENT,
     427             :                                 &mhd_ret,
     428             :                                 &policy_fulfillment_transaction,
     429             :                                 &fulfillment))
     430             :     {
     431           0 :       json_decref (output);
     432           0 :       return mhd_ret;
     433             :     }
     434             :   }
     435             : 
     436           0 :   return TALER_MHD_reply_json_steal (rc->connection,
     437             :                                      output,
     438             :                                      MHD_HTTP_OK);
     439             : }
     440             : 
     441             : 
     442             : /* end of taler-exchange-httpd_extensions.c */

Generated by: LCOV version 1.16