diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_helper.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_helper.c | 664 |
1 files changed, 567 insertions, 97 deletions
diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c index 2cffa20c..8fb5823e 100644 --- a/src/backend/taler-merchant-httpd_helper.c +++ b/src/backend/taler-merchant-httpd_helper.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014--2021 Taler Systems SA + (C) 2014--2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -20,74 +20,183 @@ */ #include "platform.h" #include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_db_lib.h> #include <taler/taler_util.h> #include <taler/taler_json_lib.h> #include "taler-merchant-httpd_helper.h" #include <taler/taler_templating_lib.h> +#include <taler/taler_dbevents.h> + + +enum GNUNET_GenericReturnValue +TMH_cmp_wire_account ( + const json_t *account, + const struct TMH_WireMethod *wm) +{ + const char *credit_facade_url = NULL; + const json_t *credit_facade_credentials = NULL; + const char *uri; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_payto_uri ("payto_uri", + &uri), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("credit_facade_url", + &credit_facade_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("credit_facade_credentials", + &credit_facade_credentials), + NULL), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + const char *ename; + unsigned int eline; + + res = GNUNET_JSON_parse (account, + ispec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse account spec: %s (%u)\n", + ename, + eline); + return GNUNET_SYSERR; + } + if (0 != + strcmp (wm->payto_uri, + uri)) + { + return GNUNET_SYSERR; + } + if ( (NULL == credit_facade_url) != + (NULL == wm->credit_facade_url) || + (NULL == credit_facade_credentials) != + (NULL == wm->credit_facade_credentials) ) + { + return GNUNET_NO; + } + if ( (NULL != credit_facade_url) && + (0 != strcmp (credit_facade_url, + wm->credit_facade_url)) ) + { + return GNUNET_NO; + } + if ( (NULL != credit_facade_credentials) && + (0 != json_equal (credit_facade_credentials, + wm->credit_facade_credentials)) ) + { + return GNUNET_NO; + } + return GNUNET_YES; +} -/** - * check @a payto_uris for well-formedness - * - * @param payto_uris JSON array of payto URIs (presumably) - * @return true if they are all valid URIs (and this is an array of strings) - */ bool -TMH_payto_uri_array_valid (const json_t *payto_uris) +TMH_accounts_array_valid (const json_t *accounts) { - bool payto_ok = true; + size_t len; - if (! json_is_array (payto_uris)) + if (! json_is_array (accounts)) { GNUNET_break_op (0); - payto_ok = false; + return false; } - else + len = json_array_size (accounts); + for (size_t i = 0; i<len; i++) { - unsigned int len = json_array_size (payto_uris); + json_t *payto_uri = json_array_get (accounts, + i); + const char *credit_facade_url = NULL; + const json_t *credit_facade_credentials = NULL; + const char *uri; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_payto_uri ("payto_uri", + &uri), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("credit_facade_url", + &credit_facade_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("credit_facade_credentials", + &credit_facade_credentials), + NULL), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + const char *ename; + unsigned int eline; + + res = GNUNET_JSON_parse (payto_uri, + ispec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse account spec: %s (%u)\n", + ename, + eline); + return false; + } - for (unsigned int i = 0; i<len; i++) + /* Test for the same payto:// URI being given twice */ + for (unsigned int j = 0; j<i; j++) { - json_t *payto_uri = json_array_get (payto_uris, - i); - const char *uri; - - if (! json_is_string (payto_uri)) - payto_ok = false; - uri = json_string_value (payto_uri); - /* Test for the same payto:// URI being given twice */ - for (unsigned int j = 0; j<i; j++) + json_t *old_uri = json_array_get (accounts, + j); + if (0 == strcmp (uri, + json_string_value ( + json_object_get (old_uri, + "payto_uri")))) { - json_t *old_uri = json_array_get (payto_uris, - j); - if (json_equal (payto_uri, - old_uri)) - { - GNUNET_break_op (0); - payto_ok = false; - break; - } + GNUNET_break_op (0); + return false; } - if (! payto_ok) - break; + } + { + char *err; + + if (NULL != + (err = TALER_payto_validate (uri))) { - char *err; - - if (NULL != - (err = TALER_payto_validate (uri))) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Encountered invalid payto://-URI `%s': %s\n", - uri, - err); - GNUNET_free (err); - payto_ok = false; - break; - } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Encountered invalid payto://-URI `%s': %s\n", + uri, + err); + GNUNET_free (err); + return false; } } - } - return payto_ok; + if ( (NULL == credit_facade_url) != + (NULL == credit_facade_credentials) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n", + uri); + return false; + } + if ( (NULL != credit_facade_url) || + (NULL != credit_facade_credentials) ) + { + struct TALER_MERCHANT_BANK_AuthenticationData auth; + + if (GNUNET_OK != + TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials, + credit_facade_url, + &auth)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid credit facade URL or credentials `%s'\n", + credit_facade_url); + return false; + } + TALER_MERCHANT_BANK_auth_free (&auth); + } + } /* end for all accounts */ + return true; } @@ -103,7 +212,7 @@ TMH_location_object_valid (const json_t *location) const char *street = NULL; const char *building = NULL; const char *building_no = NULL; - json_t *lines = NULL; + const json_t *lines = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("country", @@ -142,8 +251,8 @@ TMH_location_object_valid (const json_t *location) &building_no), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("address_lines", - &lines), + GNUNET_JSON_spec_array_const ("address_lines", + &lines), NULL), GNUNET_JSON_spec_end () }; @@ -166,13 +275,6 @@ TMH_location_object_valid (const json_t *location) size_t idx; json_t *line; - if (! json_is_array (lines)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid location for field %s\n", - "lines"); - return false; - } json_array_foreach (lines, idx, line) { if (! json_is_string (line)) @@ -204,24 +306,19 @@ TMH_products_array_valid (const json_t *products) { const char *product_id = NULL; const char *description; - json_t *description_i18n = NULL; uint64_t quantity = 0; const char *unit = NULL; struct TALER_Amount price = { .value = 0 }; const char *image_data_url = NULL; - json_t *taxes = NULL; + const json_t *taxes = NULL; struct GNUNET_TIME_Timestamp delivery_date = { 0 }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("product_id", &product_id), NULL), - GNUNET_JSON_spec_string ("description", - &description), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("description_i18n", - &description_i18n), - NULL), + TALER_JSON_spec_i18n_str ("description", + &description), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint64 ("quantity", &quantity), @@ -231,17 +328,16 @@ TMH_products_array_valid (const json_t *products) &unit), NULL), GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount ("price", - TMH_currency, - &price), + TALER_JSON_spec_amount_any ("price", + &price), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("image", &image_data_url), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("taxes", - &taxes), + GNUNET_JSON_spec_array_const ("taxes", + &taxes), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("delivery_date", @@ -276,12 +372,6 @@ TMH_products_array_valid (const json_t *products) GNUNET_break_op (0); valid = false; } - if ( (NULL != description_i18n) && - (! TALER_JSON_check_i18n (description_i18n)) ) - { - GNUNET_break_op (0); - valid = false; - } GNUNET_JSON_parse_free (spec); if (! valid) break; @@ -323,6 +413,7 @@ bool TMH_template_contract_valid (const json_t *template_contract) { const char *summary; + const char *currency; struct TALER_Amount amount = { .value = 0}; uint32_t minimum_age = 0; struct GNUNET_TIME_Relative pay_duration = { 0 }; @@ -332,9 +423,12 @@ TMH_template_contract_valid (const json_t *template_contract) &summary), NULL), GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount ("amount", - TMH_currency, - &amount), + GNUNET_JSON_spec_string ("currency", + ¤cy), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &amount), NULL), GNUNET_JSON_spec_uint32 ("minimum_age", &minimum_age), @@ -395,14 +489,17 @@ TMH_taxes_array_valid (const json_t *taxes) struct TMH_WireMethod * -TMH_setup_wire_account (const char *payto_uri) +TMH_setup_wire_account ( + const char *payto_uri, + const char *credit_facade_url, + const json_t *credit_facade_credentials) { struct TMH_WireMethod *wm; char *emsg; if (NULL != (emsg = TALER_payto_validate (payto_uri))) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid URI `%s': %s\n", payto_uri, emsg); @@ -411,6 +508,12 @@ TMH_setup_wire_account (const char *payto_uri) } wm = GNUNET_new (struct TMH_WireMethod); + if (NULL != credit_facade_url) + wm->credit_facade_url + = GNUNET_strdup (credit_facade_url); + if (NULL != credit_facade_credentials) + wm->credit_facade_credentials + = json_incref ((json_t*) credit_facade_credentials); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &wm->wire_salt, sizeof (wm->wire_salt)); @@ -471,19 +574,14 @@ TMH_check_auth_config (struct MHD_Connection *connection, TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH, - "bad authentication config")) ? - GNUNET_NO : GNUNET_SYSERR; + "bad authentication config")) + ? GNUNET_NO + : GNUNET_SYSERR; } return GNUNET_OK; } -/** - * Generate binary UUID from client-provided UUID-string. - * - * @param uuids string intpu - * @param[out] uuid set to binary UUID - */ void TMH_uuid_from_string (const char *uuids, struct GNUNET_Uuid *uuid) @@ -494,9 +592,9 @@ TMH_uuid_from_string (const char *uuids, strlen (uuids), &hc); GNUNET_static_assert (sizeof (hc) > sizeof (*uuid)); - memcpy (uuid, - &hc, - sizeof (*uuid)); + GNUNET_memcpy (uuid, + &hc, + sizeof (*uuid)); } @@ -560,7 +658,8 @@ trigger_webhook_cb (void *cls, t->rv = GNUNET_DB_STATUS_HARD_ERROR; return; } - GNUNET_assert ('\0' == ((const char *) header)[header_size-1]); + /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */ + GNUNET_assert ('\0' == ((const char *) header)[header_size]); } if (NULL != body_template) { @@ -572,13 +671,14 @@ trigger_webhook_cb (void *cls, if (0 != ret) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to expand webhook header template for webhook %llu (%d)\n", + "Failed to expand webhook body template for webhook %llu (%d)\n", (unsigned long long) webhook_serial, ret); t->rv = GNUNET_DB_STATUS_HARD_ERROR; return; } - GNUNET_assert ('\0' == ((const char *) body)[body_size-1]); + /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */ + GNUNET_assert ('\0' == ((const char *) body)[body_size]); } t->rv = TMH_db->insert_pending_webhook (TMH_db->cls, t->instance, @@ -587,6 +687,19 @@ trigger_webhook_cb (void *cls, http_method, header, body); + if (t->rv > 0) + { + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof(es)), + .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) + }; + const void *extra = NULL; + size_t extra_size = 0; + TMH_db->event_notify (TMH_db->cls, + &es, + &extra, + extra_size); + } free (header); free (body); } @@ -620,3 +733,360 @@ TMH_trigger_webhook (const char *instance, return qs; return t.rv; } + + +enum GNUNET_GenericReturnValue +TMH_base_url_by_connection (struct MHD_Connection *connection, + const char *instance, + struct GNUNET_Buffer *buf) +{ + const char *host; + const char *forwarded_host; + const char *forwarded_port; + const char *uri_path; + + memset (buf, + 0, + sizeof (*buf)); + if (NULL != TMH_base_url) + { + GNUNET_buffer_write_str (buf, + TMH_base_url); + } + else + { + if (GNUNET_YES == + TALER_mhd_is_https (connection)) + GNUNET_buffer_write_str (buf, + "https://"); + else + GNUNET_buffer_write_str (buf, + "http://"); + host = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_HOST); + forwarded_host = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Host"); + if (NULL != forwarded_host) + { + GNUNET_buffer_write_str (buf, + forwarded_host); + } + else + { + if (NULL == host) + { + GNUNET_buffer_clear (buf); + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_buffer_write_str (buf, + host); + } + forwarded_port = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Port"); + if (NULL != forwarded_port) + { + GNUNET_buffer_write_str (buf, + ":"); + GNUNET_buffer_write_str (buf, + forwarded_port); + } + uri_path = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Prefix"); + if (NULL != uri_path) + GNUNET_buffer_write_path (buf, + uri_path); + } + if (0 != strcmp (instance, + "default")) + { + GNUNET_buffer_write_path (buf, + "/instances/"); + GNUNET_buffer_write_str (buf, + instance); + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TMH_taler_uri_by_connection (struct MHD_Connection *connection, + const char *method, + const char *instance, + struct GNUNET_Buffer *buf) +{ + const char *host; + const char *forwarded_host; + const char *forwarded_port; + const char *uri_path; + + memset (buf, + 0, + sizeof (*buf)); + host = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "Host"); + forwarded_host = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Host"); + forwarded_port = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Port"); + uri_path = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + "X-Forwarded-Prefix"); + if (NULL != forwarded_host) + host = forwarded_host; + if (NULL == host) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_buffer_write_str (buf, + "taler"); + if (GNUNET_NO == TALER_mhd_is_https (connection)) + GNUNET_buffer_write_str (buf, + "+http"); + GNUNET_buffer_write_str (buf, + "://"); + GNUNET_buffer_write_str (buf, + method); + GNUNET_buffer_write_str (buf, + "/"); + GNUNET_buffer_write_str (buf, + host); + if (NULL != forwarded_port) + { + GNUNET_buffer_write_str (buf, + ":"); + GNUNET_buffer_write_str (buf, + forwarded_port); + } + if (NULL != uri_path) + GNUNET_buffer_write_path (buf, + uri_path); + if (0 != strcmp ("default", + instance)) + { + GNUNET_buffer_write_path (buf, + "instances"); + GNUNET_buffer_write_path (buf, + instance); + } + return GNUNET_OK; +} + + +/** + * Closure for #add_matching_account(). + */ +struct ExchangeMatchContext +{ + /** + * Wire method to match, NULL for all. + */ + const char *wire_method; + + /** + * Array of accounts to return. + */ + json_t *accounts; +}; + + +/** + * If the given account is feasible, add it to the array + * of accounts we return. + * + * @param cls a `struct PostReserveContext` + * @param payto_uri URI of the account + * @param conversion_url URL of a conversion service + * @param debit_restrictions restrictions for debits from account + * @param credit_restrictions restrictions for credits to account + * @param master_sig signature affirming the account + */ +static void +add_matching_account ( + void *cls, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig) +{ + struct ExchangeMatchContext *rc = cls; + char *method; + + method = TALER_payto_get_method (payto_uri); + if ( (NULL == rc->wire_method) || + (0 == strcmp (method, + rc->wire_method)) ) + { + json_t *acc; + + acc = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + payto_uri), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions) + ); + GNUNET_assert (0 == + json_array_append_new (rc->accounts, + acc)); + } + GNUNET_free (method); +} + + +/** + * Return JSON array with all of the exchange accounts + * that support the given @a wire_method. + * + * @param master_pub master public key to match exchange by + * @param wire_method NULL for any + * @return JSON array with information about all matching accounts + */ +json_t * +TMH_exchange_accounts_by_method ( + const struct TALER_MasterPublicKeyP *master_pub, + const char *wire_method) +{ + struct ExchangeMatchContext emc = { + .wire_method = wire_method, + .accounts = json_array () + }; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_assert (NULL != emc.accounts); + qs = TMH_db->select_accounts_by_exchange (TMH_db->cls, + master_pub, + &add_matching_account, + &emc); + if (qs < 0) + { + json_decref (emc.accounts); + return NULL; + } + return emc.accounts; +} + + +char * +TMH_make_order_status_url (struct MHD_Connection *con, + const char *order_id, + const char *session_id, + const char *instance_id, + struct TALER_ClaimTokenP *claim_token, + struct TALER_PrivateContractHashP *h_contract) +{ + struct GNUNET_Buffer buf; + /* Number of query parameters written so far */ + unsigned int num_qp = 0; + + GNUNET_assert (NULL != instance_id); + GNUNET_assert (NULL != order_id); + if (GNUNET_OK != + TMH_base_url_by_connection (con, + instance_id, + &buf)) + { + GNUNET_break (0); + return NULL; + } + GNUNET_buffer_write_path (&buf, + "/orders"); + GNUNET_buffer_write_path (&buf, + order_id); + if ( (NULL != claim_token) && + (! GNUNET_is_zero (claim_token)) ) + { + /* 'token=' for human readability */ + GNUNET_buffer_write_str (&buf, + "?token="); + GNUNET_buffer_write_data_encoded (&buf, + (char *) claim_token, + sizeof (*claim_token)); + num_qp++; + } + + if (NULL != session_id) + { + if (num_qp > 0) + GNUNET_buffer_write_str (&buf, + "&session_id="); + else + GNUNET_buffer_write_str (&buf, + "?session_id="); + GNUNET_buffer_write_str (&buf, + session_id); + num_qp++; + } + + if (NULL != h_contract) + { + if (num_qp > 0) + GNUNET_buffer_write_str (&buf, + "&h_contract="); + else + GNUNET_buffer_write_str (&buf, + "?h_contract="); + GNUNET_buffer_write_data_encoded (&buf, + (char *) h_contract, + sizeof (*h_contract)); + } + + return GNUNET_buffer_reap_str (&buf); +} + + +char * +TMH_make_taler_pay_uri (struct MHD_Connection *con, + const char *order_id, + const char *session_id, + const char *instance_id, + struct TALER_ClaimTokenP *claim_token) +{ + struct GNUNET_Buffer buf; + + GNUNET_assert (NULL != instance_id); + GNUNET_assert (NULL != order_id); + if (GNUNET_OK != + TMH_taler_uri_by_connection (con, + "pay", + instance_id, + &buf)) + { + GNUNET_break (0); + return NULL; + } + GNUNET_buffer_write_path (&buf, + order_id); + GNUNET_buffer_write_path (&buf, + (NULL == session_id) + ? "" + : session_id); + if ( (NULL != claim_token) && + (! GNUNET_is_zero (claim_token))) + { + /* Just 'c=' because this goes into QR + codes, so this is more compact. */ + GNUNET_buffer_write_str (&buf, + "?c="); + GNUNET_buffer_write_data_encoded (&buf, + (char *) claim_token, + sizeof (struct TALER_ClaimTokenP)); + } + + return GNUNET_buffer_reap_str (&buf); +} |