From f4585ef6c7eb890bf3ec345f032e223505a4e2d3 Mon Sep 17 00:00:00 2001 From: Christian Blättler Date: Mon, 18 Mar 2024 20:58:28 +0100 Subject: serialize v1 contract ready for db --- .../taler-merchant-httpd_private-post-orders.c | 1157 ++++++++++++-------- 1 file changed, 694 insertions(+), 463 deletions(-) (limited to 'src/backend') diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index d8120e91..cf32f886 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -22,6 +22,7 @@ * @brief the POST /orders handler * @author Christian Grothoff * @author Marcello Stanisci + * @author Christian Blättler */ #include "platform.h" #include @@ -32,11 +33,13 @@ #include #include #include +#include "taler-merchant-httpd.h" #include "taler-merchant-httpd_private-post-orders.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_private-get-orders.h" +#include "taler_merchantdb_plugin.h" /** @@ -237,6 +240,38 @@ struct OrderContext */ const char *order_id; + /** + * Summary of the contract. + */ + const char *summary; + + /** + * Internationalized summary. + */ + json_t *summary_i18n; + + /** + * URL that will show that the contract was successful + * after it has been paid for. + */ + const char *fulfillment_url; + + /** + * Message shown to the customer after paying for the contract. + * Either fulfillment_url or fulfillment_message must be specified. + */ + const char *fulfillment_message; + + /** + * Map from IETF BCP 47 language tags to localized fulfillment messages. + */ + json_t *fulfillment_message_i18n; + + /** + * Array of products that are part of the purchase. + */ + const json_t *products; + /** * URL where the same contract could be ordered again (if available). */ @@ -247,6 +282,11 @@ struct OrderContext */ const json_t *choices; + /** + * Maps authority labels to token details. Is null for v0 contracts. + */ + const json_t *token_types; + /** * Merchant base URL. */ @@ -289,26 +329,9 @@ struct OrderContext struct TALER_Amount brutto; /** - * Array of limits per currency. Is null for v0 contracts. - * See @e max_fees for v0 contracts. - */ - // const json_t *limits; - - /** - * Maximum fee as given by the client request. Only used in v0 contracts. - * See @e limits for v1 contracts. + * Maximum fee as given by the client request. */ - // struct TALER_Amount max_fee; - - /** - * Array of fee limits and wire account details by currency. - */ - struct TALER_MerchantContractLimits *limits; - - /** - * Length of the @e limits array; - */ - unsigned int limits_len; + struct TALER_Amount max_fee; /** * Specifies for how long the wallet should try to get an @@ -329,6 +352,22 @@ struct OrderContext } parse_order; + /** + * Information set in the ORDER_PHASE_PARSE_TOKEN_TYPES phase. + */ + struct + { + /** + * Array of token types referenced in the contract. + */ + struct TALER_MerchantContractTokenAuthority *authorities; + + /** + * Length of the @e authorities array. + */ + unsigned int authorities_len; + } parse_token_types; + /** * Information set in the ORDER_PHASE_PARSE_CHOICES phase. */ @@ -408,15 +447,9 @@ struct OrderContext struct { /** - * Array of fee limits and wire account details by currency. - */ - struct TALER_MerchantContractLimits *limits; - - /** - * Length of the @e limits array; + * Maximum fee */ - unsigned int limits_len; - + struct TALER_Amount max_fee; } set_max_fee; /** @@ -470,6 +503,7 @@ struct OrderContext { ORDER_PHASE_PARSE_REQUEST, ORDER_PHASE_PARSE_ORDER, + ORDER_PHASE_PARSE_TOKEN_TYPES, ORDER_PHASE_PARSE_CHOICES, ORDER_PHASE_MERGE_INVENTORY, ORDER_PHASE_ADD_PAYMENT_DETAILS, @@ -608,24 +642,15 @@ clean_order (void *cls) json_decref (oc->set_exchanges.exchanges); oc->set_exchanges.exchanges = NULL; } - /* TODO: Clean choices array */ + // TODO: Check if this is even correct for (unsigned int i = 0; iparse_choices.choices_len; i++) { - if (NULL != oc->parse_choices.choices[i].fulfillment_message_i18n) - { - json_decref (oc->parse_choices.choices[i].fulfillment_message_i18n); - oc->parse_choices.choices[i].fulfillment_message_i18n = NULL; - } - if (NULL != oc->parse_choices.choices[i].summary_i18n) - { - json_decref (oc->parse_choices.choices[i].summary_i18n); - oc->parse_choices.choices[i].summary_i18n = NULL; - } - if (NULL != oc->parse_order.delivery_location) - { - json_decref (oc->parse_order.delivery_location); - oc->parse_order.delivery_location = NULL; - } + GNUNET_array_grow (oc->parse_choices.choices[i].inputs, + oc->parse_choices.choices[i].inputs_len, + 0); + GNUNET_array_grow (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + 0); } if (NULL != oc->merge_inventory.products) { @@ -1189,9 +1214,6 @@ keys_cb ( struct OrderContext *oc = rx->oc; const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; - struct TALER_MerchantContractLimits *parsed_limits = oc->parse_order.limits; - - GNUNET_assert (1 == oc->parse_order.limits_len && NULL != parsed_limits); rx->fo = NULL; GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head, @@ -1210,7 +1232,7 @@ keys_cb ( rx->url); if ( (settings->use_stefan) && (GNUNET_OK != - TALER_amount_is_valid (&parsed_limits->max_fee)) ) + TALER_amount_is_valid (&oc->parse_order.max_fee)) ) update_stefan (oc, keys); get_acceptable (oc, @@ -1258,14 +1280,21 @@ get_exchange_keys (void *cls, rx); } +/** + * Serialize order into @a oc->serialize_order.contract, + * ready to be stored in the database. Upon success, continue + * processing with check_contract(). + * + * @param[in,out] oc order context + */ static void -serialize_order_v0 (struct OrderContext *oc) +serialize_order (struct OrderContext *oc) { const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; - struct TALER_MerchantContractChoice *choice = oc->parse_choices.choices; - struct TALER_MerchantContractLimits *limits = oc->set_max_fee.limits; json_t *merchant = NULL; + json_t *token_types = NULL; + json_t *choices = NULL; { merchant = GNUNET_JSON_PACK ( @@ -1313,196 +1342,115 @@ serialize_order_v0 (struct OrderContext *oc) } } - // TODO: How to properly handle these cases / errors? - GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice); - GNUNET_assert (1 == oc->set_max_fee.limits_len && NULL != limits); - - oc->serialize_order.contract = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("summary", - choice->summary), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("summary_i18n", - choice->summary_i18n)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("public_reorder_url", - oc->parse_order.public_reorder_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_message", - choice->fulfillment_message)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n", - choice->fulfillment_message_i18n)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_url", - choice->fulfillment_url)), - GNUNET_JSON_pack_array_incref ("products", - oc->merge_inventory.products), - GNUNET_JSON_pack_data_auto ("h_wire", - &oc->add_payment_details.wm->h_wire), - GNUNET_JSON_pack_string ("wire_method", - oc->add_payment_details.wm->wire_method), - GNUNET_JSON_pack_string ("order_id", - oc->parse_order.order_id), - GNUNET_JSON_pack_timestamp ("timestamp", - oc->parse_order.timestamp), - GNUNET_JSON_pack_timestamp ("pay_deadline", - oc->parse_order.pay_deadline), - GNUNET_JSON_pack_timestamp ("wire_transfer_deadline", - oc->parse_order.wire_deadline), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_timestamp ("delivery_date", - oc->parse_order.delivery_date)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("delivery_location", - oc->parse_order.delivery_location)), - GNUNET_JSON_pack_string ("merchant_base_url", - oc->parse_order.merchant_base_url), - GNUNET_JSON_pack_object_incref ("merchant", - merchant), - GNUNET_JSON_pack_data_auto ("merchant_pub", - &oc->hc->instance->merchant_pub), - GNUNET_JSON_pack_array_incref ("exchanges", - oc->set_exchanges.exchanges), - TALER_JSON_pack_amount ("max_fee", - &limits->max_fee), - TALER_JSON_pack_amount ("amount", - &oc->parse_order.brutto), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("extra", - (json_t *) oc->parse_order.extra)) - ); - - /* Pack does not work here, because it doesn't set zero-values for timestamps */ - GNUNET_assert (0 == - json_object_set_new (oc->serialize_order.contract, - "refund_deadline", - GNUNET_JSON_from_timestamp ( - oc->parse_order.refund_deadline))); - GNUNET_log ( - GNUNET_ERROR_TYPE_INFO, - "Refund deadline for contact is %llu\n", - (unsigned long long) oc->parse_order.refund_deadline.abs_time.abs_value_us); - GNUNET_log ( - GNUNET_ERROR_TYPE_INFO, - "Wallet timestamp for contact is %llu\n", - (unsigned long long) oc->parse_order.timestamp.abs_time.abs_value_us); - - /* Pack does not work here, because it sets zero-values for relative times */ - /* auto_refund should only be set if it is not 0 */ - if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund)) { - GNUNET_assert (0 == - json_object_set_new (oc->serialize_order.contract, - "auto_refund", - GNUNET_JSON_from_time_rel ( - oc->parse_order.auto_refund))); + for (unsigned int i = 0; iparse_token_types.authorities_len; i++) + { + struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_token_types.authorities[i]; + + // TODO: What information about a token family do we want to store in the db? + json_t *jauthority = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("summary", + authority->summary), + GNUNET_JSON_pack_data_auto ("h_pub", + &authority->key.public_key.pub_key_hash), + GNUNET_JSON_pack_timestamp ("token_expiration", + authority->token_expiration) + ); + + json_object_set_new (token_types, authority->label, jauthority); + } } -} - -static void -serialize_order_v1 (struct OrderContext *oc) -{ - const struct TALER_MERCHANTDB_InstanceSettings *settings = - &oc->hc->instance->settings; - json_t *merchant = NULL; - json_t *limits = json_object (); - json_t *choices = json_array (); { - merchant = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("name", - settings->name), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("website", - settings->website)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("email", - settings->email)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("logo", - settings->logo))); - GNUNET_assert (NULL != merchant); + for (unsigned int i = 0; iparse_choices.choices_len; i++) { - json_t *loca; + struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; - /* Handle merchant address */ - loca = settings->address; - if (NULL != loca) + json_t *inputs = NULL; + json_t *outputs = NULL; + + for (unsigned int j = 0; jinputs_len; j++) { - loca = json_deep_copy (loca); - GNUNET_assert (NULL != loca); - GNUNET_assert (0 == - json_object_set_new (merchant, - "address", - loca)); + struct TALER_MerchantContractInput *input = &choice->inputs[j]; + + json_t *jinput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + input->type) + ); + + if (TALER_MCIT_TOKEN == input->type) + { + GNUNET_assert(0 == + json_object_set_new(jinput, + "number", + json_integer ( + input->details.token.number))); + GNUNET_assert(0 == + json_object_set_new(jinput, + "token_authority_label", + json_string ( + input->details.token.token_authority_label))); + } + + json_array_append_new (inputs, jinput); } - } - { - json_t *juri; - /* Handle merchant jurisdiction */ - juri = settings->jurisdiction; - if (NULL != juri) + for (unsigned int j = 0; joutputs_len; j++) { - juri = json_deep_copy (juri); - GNUNET_assert (NULL != juri); - GNUNET_assert (0 == - json_object_set_new (merchant, - "jurisdiction", - juri)); + struct TALER_MerchantContractOutput *output = &choice->outputs[j]; + + json_t *joutput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + output->type) + ); + + if (TALER_MCOT_TOKEN == output->type) + { + GNUNET_assert(0 == + json_object_set_new(joutput, + "number", + json_integer ( + output->details.token.number))); + + GNUNET_assert(0 == + json_object_set_new(joutput, + "token_authority_label", + json_string ( + output->details.token.token_authority_label))); + } + + json_array_append_new (outputs, joutput); } - } - } - for (unsigned int i = 0; iset_max_fee.limits_len; i++) - { - struct TALER_MerchantContractLimits l = oc->set_max_fee.limits[i]; - GNUNET_assert (0 == - json_object_set_new( - limits, - l.currency, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("h_wire", - &l.h_wire), - GNUNET_JSON_pack_string ("wire_method", - l.wire_method), - TALER_JSON_pack_amount("max_fee", - &l.max_fee)))); - } + json_t *jchoice = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); - for (unsigned int i = 0; iparse_choices.choices_len; i++) - { - struct TALER_MerchantContractChoice choice = oc->parse_choices.choices[i]; - GNUNET_assert (0 == - json_array_append_new( - choices, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("summary", - choice.summary), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("summary_i18n", - choice.summary_i18n)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_message", - choice.fulfillment_message)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n", - choice.fulfillment_message_i18n)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_url", - choice.fulfillment_url))))); + json_array_append_new (choices, jchoice); + } } oc->serialize_order.contract = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("summary", + oc->parse_order.summary), GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("choices", - choices)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("limits", - limits)), + GNUNET_JSON_pack_object_incref ("summary_i18n", + oc->parse_order.summary_i18n)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("public_reorder_url", oc->parse_order.public_reorder_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_message", + oc->parse_order.fulfillment_message)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n", + oc->parse_order.fulfillment_message_i18n)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_url", + oc->parse_order.fulfillment_url)), GNUNET_JSON_pack_array_incref ("products", oc->merge_inventory.products), GNUNET_JSON_pack_data_auto ("h_wire", @@ -1531,8 +1479,18 @@ serialize_order_v1 (struct OrderContext *oc) &oc->hc->instance->merchant_pub), GNUNET_JSON_pack_array_incref ("exchanges", oc->set_exchanges.exchanges), + TALER_JSON_pack_amount ("max_fee", + &oc->parse_order.max_fee), TALER_JSON_pack_amount ("amount", &oc->parse_order.brutto), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref("choices", + choices) + ), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref("token_types", + token_types) + ), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("extra", (json_t *) oc->parse_order.extra)) @@ -1544,6 +1502,7 @@ serialize_order_v1 (struct OrderContext *oc) "refund_deadline", GNUNET_JSON_from_timestamp ( oc->parse_order.refund_deadline))); + GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "Refund deadline for contact is %llu\n", @@ -1563,26 +1522,6 @@ serialize_order_v1 (struct OrderContext *oc) GNUNET_JSON_from_time_rel ( oc->parse_order.auto_refund))); } -} - -/** - * Serialize order into @a oc->serialize_order.contract, - * ready to be stored in the database. Upon success, continue - * processing with check_contract(). - * - * @param[in,out] oc order context - */ -static void -serialize_order (struct OrderContext *oc) -{ - if (TALER_MCV_V0 == oc->parse_order.version) - { - serialize_order_v0 (oc); - } - else - { - serialize_order_v1 (oc); - } oc->phase++; } @@ -1600,33 +1539,28 @@ set_max_fee (struct OrderContext *oc) const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; - GNUNET_array_grow(oc->set_max_fee.limits, - oc->set_max_fee.limits_len, - oc->parse_order.limits_len); - - for (unsigned int i = 0; iparse_order.limits_len; i++) + if (GNUNET_OK != + TALER_amount_is_valid (&oc->parse_order.max_fee)) { - if (GNUNET_OK != - TALER_amount_is_valid (&oc->parse_order.limits[i].max_fee)) - { - struct TALER_Amount stefan; + struct TALER_Amount stefan; - if ( (settings->use_stefan) && - (GNUNET_OK == - TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ) - stefan = oc->set_exchanges.max_stefan_fee; - else - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (oc->parse_order.brutto.currency, - &stefan)); - oc->set_max_fee.limits[i].max_fee = stefan; - } + if ( (settings->use_stefan) && + (GNUNET_OK == + TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ) + stefan = oc->set_exchanges.max_stefan_fee; + else + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (oc->parse_order.brutto.currency, + &stefan)); + oc->set_max_fee.max_fee = stefan; + } + else + { + oc->set_max_fee.max_fee = oc->parse_order.max_fee; } - oc->phase++; } - /** * Set list of acceptable exchanges in @a oc. Upon success, continue * processing with set_max_fee(). @@ -1693,28 +1627,22 @@ set_exchanges (struct OrderContext *oc) /** * Parse the order field of the request. Upon success, continue - * processing with parse_choices(). + * processing with parse_token_types(). * * @param[in,out] oc order context */ static void parse_order (struct OrderContext *oc) { - // struct TALER_MerchantContractLimits *limits; - const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; const char *merchant_base_url = NULL; const char *version = NULL; const json_t *jmerchant = NULL; - const json_t *jlimits; - struct TALER_Amount max_fee; - // limits = GNUNET_new (struct TALER_MerchantContractLimits); /* auto_refund only needs to be type-checked, * mostly because in GNUnet relative times can't * be negative. */ bool no_fee; - bool no_limits; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("version", @@ -1722,10 +1650,32 @@ parse_order (struct OrderContext *oc) NULL), TALER_JSON_spec_amount_any ("amount", &oc->parse_order.brutto), + GNUNET_JSON_spec_string ("summary", + &oc->parse_order.summary), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("products", + &oc->parse_order.products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("summary_i18n", + &oc->parse_order.summary_i18n), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("order_id", &oc->parse_order.order_id), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("fulfillment_message", + &oc->parse_order.fulfillment_message), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("fulfillment_message_i18n", + &oc->parse_order.fulfillment_message_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("fulfillment_url", + &oc->parse_order.fulfillment_url), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("public_reorder_url", &oc->parse_order.public_reorder_url), @@ -1742,6 +1692,10 @@ parse_order (struct OrderContext *oc) GNUNET_JSON_spec_object_const ("merchant", &jmerchant), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("token_types", + &oc->parse_order.token_types), + NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("timestamp", &oc->parse_order.timestamp), @@ -1760,12 +1714,8 @@ parse_order (struct OrderContext *oc) NULL), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("max_fee", - &max_fee), + &oc->parse_order.max_fee), &no_fee), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("limits", - &jlimits), - &no_limits), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("delivery_location", &oc->parse_order.delivery_location), @@ -1802,82 +1752,31 @@ parse_order (struct OrderContext *oc) { oc->parse_order.version = TALER_MCV_V0; - if ( (! no_fee) && - (GNUNET_OK != - TALER_amount_cmp_currency (&oc->parse_order.brutto, - &max_fee)) ) + if (NULL != oc->parse_order.choices) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); reply_with_error (oc, MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - "different currencies used for 'max_fee' and 'amount' currency"); + TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR, + "choices array must be null for v0 contracts"); return; } - /** - * v0 only supports one currency. For the sake of simplicity, - * we still use the limits array with just one item. - */ - GNUNET_array_grow (oc->parse_order.limits, - oc->parse_order.limits_len, - 1); - - oc->parse_order.limits->max_fee = max_fee; - - /* Copy currency of max_fee into limits label */ - GNUNET_snprintf (oc->parse_order.limits->currency, - sizeof (oc->parse_order.limits->currency), - "%s", - oc->parse_order.limits->max_fee.currency); + if (NULL != oc->parse_order.token_types) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR, + "token_types object must be null for v0 contracts"); + return; + } } else if (0 != strcmp("v1", version)) { oc->parse_order.version = TALER_MCV_V1; - - GNUNET_array_grow (oc->parse_order.limits, - oc->parse_order.limits_len, - json_array_size (jlimits)); - - for (unsigned int i = 0; iparse_order.limits_len; i++) - { - struct TALER_MerchantContractLimits *limits = &oc->parse_order.limits[i]; - const char *error_name; - unsigned int error_line; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount_any ("max_fee", - &limits->max_fee), - // TODO: Add the rest of the fields - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (json_array_get (jlimits, - i), - ispec, - &error_name, - &error_line); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Limits parsing failed at #%u: %s:%u\n", - i, - error_name, - error_line); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "order.limits"); - return; - } - - /* Copy currency of max_fee into limits label */ - GNUNET_snprintf (limits->currency, - sizeof (limits->currency), - "%s", - limits->max_fee.currency); - } } else { @@ -1910,6 +1809,19 @@ parse_order (struct OrderContext *oc) "no trusted exchange for this currency"); return; } + if ( (! no_fee) && + (GNUNET_OK != + TALER_amount_cmp_currency (&oc->parse_order.brutto, + &oc->parse_order.max_fee)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + "different currencies used for 'max_fee' and 'amount' currency"); + return; + } /* Add order_id if it doesn't exist. */ if (NULL == oc->parse_order.order_id) @@ -1957,6 +1869,46 @@ parse_order (struct OrderContext *oc) GNUNET_assert (NULL != oc->parse_order.order_id); } + /* Patch fulfillment URL with order_id (implements #6467). */ + if (NULL != oc->parse_order.fulfillment_url) + { + const char *pos; + + pos = strstr (oc->parse_order.fulfillment_url, + "${ORDER_ID}"); + if (NULL != pos) + { + /* replace ${ORDER_ID} with the real order_id */ + char *nurl; + + /* We only allow one placeholder */ + if (strstr (pos + strlen ("${ORDER_ID}"), + "${ORDER_ID}")) + { + GNUNET_break_op (0); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "fulfillment_url"); + return; + } + + GNUNET_asprintf (&nurl, + "%.*s%s%s", + /* first output URL until ${ORDER_ID} */ + (int) (pos - oc->parse_order.fulfillment_url), + oc->parse_order.fulfillment_url, + /* replace ${ORDER_ID} with the right order_id */ + oc->parse_order.order_id, + /* append rest of original URL */ + pos + strlen ("${ORDER_ID}")); + + oc->parse_order.fulfillment_url = GNUNET_strdup (nurl); + + GNUNET_free (nurl); + } + } + /* Check soundness of refund deadline, and that a timestamp * is actually present. */ { @@ -2099,6 +2051,18 @@ parse_order (struct OrderContext *oc) oc->parse_order.merchant_base_url = url; } + if ( (NULL != oc->parse_order.products) && + (! TMH_products_array_valid (oc->parse_order.products)) ) + { + GNUNET_break_op (0); + reply_with_error ( + oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "order.products"); + return; + } + /* Merchant information must not already be present */ if (NULL != jmerchant) { @@ -2126,165 +2090,433 @@ parse_order (struct OrderContext *oc) } /** - * Parse a json contract choice. - * - * @param[out] choice resulting contract choice - * @param root json object - * @param order_id order_id to be replaced in fulfillment URL - * @return #GNUNET_OK if choice could be parsed. + * Parse the token_types map of the request. Upon success, continue + * processing with parse_choices(). */ -static enum GNUNET_GenericReturnValue -parse_choice ( - struct TALER_MerchantContractChoice *choice, - json_t *root, - const char *order_id) +static void +parse_token_types (struct OrderContext *oc) { - const char *error_name; - unsigned int error_line; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("summary", - &choice->summary), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("products", - &choice->products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("summary_i18n", - &choice->summary_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_message", - &choice->fulfillment_message), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("fulfillment_message_i18n", - &choice->fulfillment_message_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("fulfillment_url", - &choice->fulfillment_url), - NULL), - }; - - enum GNUNET_GenericReturnValue ret; - ret = GNUNET_JSON_parse (root, - spec, - &error_name, - &error_line); - if (GNUNET_OK != ret) + if (NULL != oc->parse_order.token_types) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Choice parsing failed: %s:%u\n", - error_name, - error_line); - return ret; - } + const char *key; + json_t *value; - /* Patch fulfillment URL with order_id (implements #6467). */ - if (NULL != choice->fulfillment_url) - { - const char *pos; + GNUNET_array_grow (oc->parse_token_types.authorities, + oc->parse_token_types.authorities_len, + json_object_size(oc->parse_order.token_types)); - pos = strstr (choice->fulfillment_url, - "${ORDER_ID}"); - if (NULL != pos) + json_object_foreach ((json_t*) oc->parse_order.token_types, + key, + value) { - /* replace ${ORDER_ID} with the real order_id */ - char *nurl; + struct TALER_MerchantContractTokenAuthority authority; + const struct json_t *pub_key; - /* We only allow one placeholder */ - if (strstr (pos + strlen ("${ORDER_ID}"), - "${ORDER_ID}")) + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_object_const ("key", &pub_key), + GNUNET_JSON_spec_bool ("critical", &authority.critical), + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, + NULL)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Patching fulfillment URL failed\n"); - return GNUNET_SYSERR; + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "token_types"); + return; } - GNUNET_asprintf (&nurl, - "%.*s%s%s", - /* first output URL until ${ORDER_ID} */ - (int) (pos - choice->fulfillment_url), - choice->fulfillment_url, - /* replace ${ORDER_ID} with the right order_id */ - order_id, - /* append rest of original URL */ - pos + strlen ("${ORDER_ID}")); + const char *cipher = NULL; - choice->fulfillment_url = GNUNET_strdup (nurl); + struct GNUNET_JSON_Specification kspec[] = { + GNUNET_JSON_spec_string ("cipher", &cipher), + GNUNET_JSON_spec_rsa_public_key("public_key", &authority.key.public_key.details.rsa_public_key), + }; - GNUNET_free (nurl); - } - } + if (GNUNET_OK != + GNUNET_JSON_parse (pub_key, + kspec, + NULL, + NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (kspec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "token_types.key"); + return; + } - if ( (NULL != choice->products) && - (! TMH_products_array_valid (choice->products)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid choice.products array\n"); - return GNUNET_SYSERR; + if (0 == strcmp("rsa", cipher)) + { + authority.key.public_key.cipher = GNUNET_CRYPTO_BSA_RSA; + } + else + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (kspec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "token_types.key.cipher"); + return; + } + + GNUNET_CRYPTO_rsa_public_key_hash ( + authority.key.public_key.details.rsa_public_key, + &authority.key.public_key.pub_key_hash); + + struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_token_family_key (TMH_db->cls, + oc->hc->instance->settings.id, + &authority.key.public_key.pub_key_hash, + &key_details); + + if (qs <= 0) + { + enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + unsigned int http_status = 0; + + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_FETCH_FAILED; + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Token family public key unknown\n"); + http_status = MHD_HTTP_NOT_FOUND; + // TODO: Add error code + ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* case listed to make compilers happy */ + GNUNET_assert (0); + } + json_decref (oc->merge_inventory.products); + reply_with_error (oc, + http_status, + ec, + "token family public key unknown"); + return; + } + + strcpy((char *) authority.label, key); + strcpy((char *) authority.summary, key_details.token_family.description); + // TODO: Copy summary_i18n + + switch (key_details.token_family.kind) { + case TALER_MERCHANTDB_TFK_Subscription: + authority.kind = TALER_MCTK_SUBSCRIPTION; + break; + case TALER_MERCHANTDB_TFK_Discount: + authority.kind = TALER_MCTK_DISCOUNT; + break; + } + + GNUNET_array_append (oc->parse_token_types.authorities, + oc->parse_token_types.authorities_len, + authority); + } } - return GNUNET_OK; + oc->phase++; } /** - * Check contract version and call parse_choices_v0 or parse_choices_v1 - * respectively. Upon success, continue processing with merge_inventory(). + * Parse contract choices. Upon success, continue + * processing with merge_inventory(). * * @param[in,out] oc order context */ static void parse_choices (struct OrderContext *oc) { - enum GNUNET_GenericReturnValue ret; - - if (TALER_MCV_V0 == oc->parse_order.version) + if (NULL != oc->parse_order.choices) { - /** - * v0 contract is basically a v1 contract with only one choice - * and no inputs or outputs. - */ - GNUNET_array_grow (oc->parse_choices.choices, - oc->parse_choices.choices_len, - 1); - - ret = parse_choice(oc->parse_choices.choices, - oc->parse_request.order, - oc->parse_order.order_id); - - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "order"); - return; - } - } - else if (TALER_MCV_V1 == oc->parse_order.version) - { - GNUNET_assert (NULL != oc->parse_order.choices); - GNUNET_array_grow (oc->parse_choices.choices, oc->parse_choices.choices_len, json_array_size (oc->parse_order.choices)); for (unsigned int i = 0; iparse_choices.choices_len; i++) { - ret = parse_choice(&oc->parse_choices.choices[i], - json_array_get (oc->parse_order.choices, i), - oc->parse_order.order_id); + const char *error_name; + unsigned int error_line; + const json_t *jinputs = NULL; + const json_t *joutputs = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("inputs", + &jinputs), + GNUNET_JSON_spec_array_const ("outputs", + &joutputs), + }; + enum GNUNET_GenericReturnValue ret; + ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i), + spec, + &error_name, + &error_line); if (GNUNET_OK != ret) + { + GNUNET_JSON_parse_free (spec); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Choice parsing failed: %s:%u\n", + error_name, + error_line); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "choice json parse failed"); + return; + } + + if (! json_is_array (jinputs) || + ! json_is_array (joutputs)) { GNUNET_break_op (0); reply_with_error (oc, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "choices"); + "choice"); return; } + + { + const json_t *jinput; + size_t idx; + json_array_foreach ((json_t *) jinputs, idx, jinput) + { + struct TALER_MerchantContractInput input; + const char *kind = NULL; + bool no_number; + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("kind", + &kind), + GNUNET_JSON_spec_string ("authority_label", + &input.details.token.token_authority_label), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("number", + &input.details.token.number), + &no_number), + }; + + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (jinput, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u for field %s\n", + (unsigned int) idx, + ename); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "input json parse failed"); + return; + } + + /** + * Parse kind of input. For now, only 'token' is supported. + */ + if (0 == strcmp("token", kind)) + { + input.type = TALER_MCIT_TOKEN; + } + else + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u for field kind\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid kind field in input"); + return; + } + + /** + * Set default number to 1 if not present. + */ + if (no_number) + { + input.details.token.number = 1; + } + else if (1 > input.details.token.number) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u for field number\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid number field in input"); + return; + } + + bool found = false; + + for (unsigned int i = 0; iparse_token_types.authorities_len; i++) + { + if (0 == strcmp (oc->parse_token_types.authorities[i].label, + input.details.token.token_authority_label)) + { + found = true; + break; + } + } + + if (! found) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u: authority_label not found in token_types\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid authority_label field in input"); + return; + } + + GNUNET_array_append (oc->parse_choices.choices[i].inputs, + oc->parse_choices.choices[i].inputs_len, + input); + } + } + + { + const json_t *joutput; + size_t idx; + json_array_foreach ((json_t *) joutputs, idx, joutput) + { + struct TALER_MerchantContractOutput output; + const char *kind = NULL; + bool no_number; + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("kind", + &kind), + GNUNET_JSON_spec_string ("authority_label", + &output.details.token.token_authority_label), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("number", + &output.details.token.number), + &no_number), + }; + + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (joutput, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u for field %s\n", + (unsigned int) idx, + ename); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "output json parse failed"); + return; + } + + /** + * Parse kind of output. For now, only 'token' is supported. + */ + if (0 == strcmp("token", kind)) + { + output.type = TALER_MCOT_TOKEN; + } + else + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u for field kind\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid kind field in output"); + return; + } + + /** + * Set default number to 1 if not present. + */ + if (no_number) + { + output.details.token.number = 1; + } + else if (1 > output.details.token.number) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u for field number\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid number field in output"); + return; + } + + bool found = false; + + for (unsigned int i = 0; iparse_token_types.authorities_len; i++) + { + if (0 == strcmp (oc->parse_token_types.authorities[i].label, + output.details.token.token_authority_label)) + { + found = true; + break; + } + } + + if (! found) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u: authority_label not found in token_types\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid authority_label field in output"); + return; + } + + GNUNET_array_append (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + output); + } + } } } @@ -2337,17 +2569,13 @@ add_payment_details (struct OrderContext *oc) static void merge_inventory (struct OrderContext *oc) { - struct TALER_MerchantContractChoice *choice = oc->parse_choices.choices; - - GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice); - /** * parse_request.inventory_products => instructions to add products to contract terms * parse_order.products => contains products that are not from the backend-managed inventory. */ - if (NULL != choice->products) + if (NULL != oc->parse_order.products) oc->merge_inventory.products - = json_deep_copy (choice->products); + = json_deep_copy (oc->parse_order.products); else oc->merge_inventory.products = json_array (); @@ -2676,6 +2904,9 @@ TMH_private_post_orders ( case ORDER_PHASE_PARSE_ORDER: parse_order (oc); break; + case ORDER_PHASE_PARSE_TOKEN_TYPES: + parse_token_types (oc); + break; case ORDER_PHASE_PARSE_CHOICES: parse_choices (oc); break; -- cgit v1.2.3