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