diff options
author | Christian Blättler <blatc2@bfh.ch> | 2024-04-10 08:01:28 +0200 |
---|---|---|
committer | Christian Blättler <blatc2@bfh.ch> | 2024-04-10 08:01:28 +0200 |
commit | d42cb68e0ce55310aac0ed0fb83e57ede8e63836 (patch) | |
tree | c26cd5c4704eed3e87a45b52a5f5fac7026d8049 /src/backend/taler-merchant-httpd_private-post-orders.c | |
parent | 983ad0943b4f0b9ddf7d1b7a14693e4b69395ea6 (diff) | |
parent | d4fd038d116381d76d1fdf8384101f8fa901ffe5 (diff) | |
download | merchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.tar.gz merchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.tar.bz2 merchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.zip |
Merge branch 'master' into tokens
# Conflicts:
# src/backend/taler-merchant-httpd_private-post-orders.c
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 440 |
1 files changed, 245 insertions, 195 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 8a093813..b2070bcc 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -49,6 +49,11 @@ #define MAX_RETRIES 3 /** + * Maximum number of inventory products per order. + */ +#define MAX_PRODUCTS 1024 + +/** * What is the label under which we find/place the merchant's * jurisdiction in the locations list by default? */ @@ -346,6 +351,11 @@ struct OrderContext */ const json_t *extra; + /** + * Minimum age required by the order. + */ + uint32_t minimum_age; + } parse_order; /** @@ -451,6 +461,24 @@ struct OrderContext * Which product (by offset) is out of stock, UINT_MAX if all were in-stock. */ unsigned int out_of_stock_index; + + /** + * Set to a previous claim token *if* @e idempotent + * is also true. + */ + struct TALER_ClaimTokenP token; + + /** + * Set to true if the order was idempotent and there + * was an equivalent one before. + */ + bool idempotent; + + /** + * Set to true if the order is in conflict with a + * previous order with the same order ID. + */ + bool conflict; } execute_order; struct @@ -668,7 +696,6 @@ clean_order (void *cls) oc->parse_request.uuids_length, 0); json_decref (oc->parse_request.order); - /* TODO: Check that all other fields are cleaned up! */ json_decref (oc->serialize_order.contract); GNUNET_free (oc->parse_order.merchant_base_url); GNUNET_free (oc); @@ -695,6 +722,45 @@ execute_transaction (struct OrderContext *oc) GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } + + /* Test if we already have an order with this id */ + { + json_t *contract_terms; + struct TALER_MerchantPostDataHashP orig_post; + + qs = TMH_db->lookup_order (TMH_db->cls, + oc->hc->instance->settings.id, + oc->parse_order.order_id, + &oc->execute_order.token, + &orig_post, + &contract_terms); + /* If yes, check for idempotency */ + if (0 > qs) + { + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + TMH_db->rollback (TMH_db->cls); + json_decref (contract_terms); + /* Comparing the contract terms is sufficient because all the other + params get added to it at some point. */ + if (0 == GNUNET_memcmp (&orig_post, + &oc->parse_request.h_post_data)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order creation idempotent\n"); + oc->execute_order.idempotent = true; + return qs; + } + GNUNET_break_op (0); + oc->execute_order.conflict = true; + return qs; + } + } + /* Setup order */ qs = TMH_db->insert_order (TMH_db->cls, oc->hc->instance->settings.id, @@ -791,70 +857,6 @@ execute_order (struct OrderContext *oc) &oc->hc->instance->settings; enum GNUNET_DB_QueryStatus qs; - /* Test if we already have an order with this id */ - /* FIXME: this should be done within the main - transaction! */ - { - struct TALER_ClaimTokenP token; - json_t *contract_terms; - struct TALER_MerchantPostDataHashP orig_post; - - TMH_db->preflight (TMH_db->cls); - qs = TMH_db->lookup_order (TMH_db->cls, - oc->hc->instance->settings.id, - oc->parse_order.order_id, - &token, - &orig_post, - &contract_terms); - /* If yes, check for idempotency */ - if (0 > qs) - { - GNUNET_break (0); - TMH_db->rollback (TMH_db->cls); - reply_with_error (oc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_order"); - return; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - MHD_RESULT ret; - - json_decref (contract_terms); - /* Comparing the contract terms is sufficient because all the other - params get added to it at some point. */ - if (0 == GNUNET_memcmp (&orig_post, - &oc->parse_request.h_post_data)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Order creation idempotent\n"); - ret = TALER_MHD_REPLY_JSON_PACK ( - oc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_string ("order_id", - oc->parse_order.order_id), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_varsize ( - "token", - GNUNET_is_zero (&token) - ? NULL - : &token, - sizeof (token)))); - finalize_order (oc, - ret); - return; - } - /* This request is not idempotent */ - GNUNET_break_op (0); - reply_with_error ( - oc, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, - oc->parse_order.order_id); - return; - } - } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Executing database transaction to create order '%s' for instance '%s'\n", oc->parse_order.order_id, @@ -899,6 +901,37 @@ execute_order (struct OrderContext *oc) return; } + /* DB transaction succeeded, check for idempotent */ + if (oc->execute_order.idempotent) + { + MHD_RESULT ret; + + ret = TALER_MHD_REPLY_JSON_PACK ( + oc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("order_id", + oc->parse_order.order_id), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_varsize ( + "token", + GNUNET_is_zero (&oc->execute_order.token) + ? NULL + : &oc->execute_order.token, + sizeof (oc->execute_order.token)))); + finalize_order (oc, + ret); + return; + } + if (oc->execute_order.conflict) + { + reply_with_error ( + oc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, + oc->parse_order.order_id); + return; + } + /* DB transaction succeeded, check for out-of-stock */ if (oc->execute_order.out_of_stock_index < UINT_MAX) { @@ -1436,161 +1469,155 @@ serialize_order (struct OrderContext *oc) { const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; - json_t *merchant = NULL; + json_t *merchant; json_t *token_types = 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); { - 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); - { - json_t *loca; + json_t *loca; - /* Handle merchant address */ - loca = settings->address; - if (NULL != loca) - { - loca = json_deep_copy (loca); - GNUNET_assert (NULL != loca); - GNUNET_assert (0 == - json_object_set_new (merchant, - "address", - loca)); - } - } + /* Handle merchant address */ + loca = settings->address; + if (NULL != loca) { - json_t *juri; - - /* Handle merchant jurisdiction */ - juri = settings->jurisdiction; - if (NULL != juri) - { - juri = json_deep_copy (juri); - GNUNET_assert (NULL != juri); - GNUNET_assert (0 == - json_object_set_new (merchant, - "jurisdiction", - juri)); - } + loca = json_deep_copy (loca); + GNUNET_assert (NULL != loca); + GNUNET_assert (0 == + json_object_set_new (merchant, + "address", + loca)); } } - { - for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) - { - struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; - - // TODO: Finish spec to clearly define how token families are stored in - // ContractTerms. - // Here are some thoughts: - // - Multiple keys of the same token family can be referrenced in - // one contract. E.g. exchanging old subscription for new. - // - TokenAuthority should be renamed to TokenFamily for consistency. - // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but - // every token-based in- or ouput needs to have a valid_after date, - // so it's clear with key is referenced. - json_t *jauthority = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("description", - authority->description), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("description_i18n", - authority->description_i18n)), - GNUNET_JSON_pack_data_auto ("h_pub", - &authority->pub->public_key.pub_key_hash), - GNUNET_JSON_pack_data_auto ("pub", - &authority->pub->public_key.pub_key_hash), - GNUNET_JSON_pack_timestamp ("token_expiration", - authority->token_expiration) - ); + json_t *juri; + /* Handle merchant jurisdiction */ + juri = settings->jurisdiction; + if (NULL != juri) + { + juri = json_deep_copy (juri); + GNUNET_assert (NULL != juri); GNUNET_assert (0 == - json_object_set_new (token_types, - authority->label, - jauthority)); + json_object_set_new (merchant, + "jurisdiction", + juri)); } } + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) { - for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) - { - struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; + struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; - json_t *inputs = json_array (); - json_t *outputs = json_array (); + // TODO: Finish spec to clearly define how token families are stored in + // ContractTerms. + // Here are some thoughts: + // - Multiple keys of the same token family can be referrenced in + // one contract. E.g. exchanging old subscription for new. + // - TokenAuthority should be renamed to TokenFamily for consistency. + // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but + // every token-based in- or ouput needs to have a valid_after date, + // so it's clear with key is referenced. + json_t *jauthority = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + authority->description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + authority->description_i18n)), + GNUNET_JSON_pack_data_auto ("h_pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_data_auto ("pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_timestamp ("token_expiration", + authority->token_expiration) + ); - for (unsigned int j = 0; j<choice->inputs_len; j++) - { - struct TALER_MerchantContractInput *input = &choice->inputs[j]; + GNUNET_assert (0 == + json_object_set_new (token_types, + authority->label, + jauthority)); + } - json_t *jinput = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_int64 ("type", - input->type) - ); + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; - if (TALER_MCIT_TOKEN == input->type) - { - GNUNET_assert(0 == - json_object_set_new(jinput, - "number", - json_integer ( - input->details.token.count))); - GNUNET_assert(0 == - json_object_set_new(jinput, - "token_family_slug", - json_string ( - input->details.token.token_family_slug))); - } + json_t *inputs = json_array (); + json_t *outputs = json_array (); - GNUNET_assert (0 == json_array_append_new (inputs, jinput)); - } + for (unsigned int j = 0; j<choice->inputs_len; j++) + { + struct TALER_MerchantContractInput *input = &choice->inputs[j]; - for (unsigned int j = 0; j<choice->outputs_len; j++) - { - struct TALER_MerchantContractOutput *output = &choice->outputs[j]; + json_t *jinput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + input->type) + ); - json_t *joutput = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_int64 ("type", - output->type) - ); + if (TALER_MCIT_TOKEN == input->type) + { + GNUNET_assert(0 == + json_object_set_new(jinput, + "number", + json_integer ( + input->details.token.count))); + GNUNET_assert(0 == + json_object_set_new(jinput, + "token_family_slug", + json_string ( + input->details.token.token_family_slug))); + } - if (TALER_MCOT_TOKEN == output->type) - { - GNUNET_assert(0 == - json_object_set_new(joutput, - "number", - json_integer ( - output->details.token.count))); - - GNUNET_assert(0 == - json_object_set_new(joutput, - "token_family_slug", - json_string ( - output->details.token.token_family_slug))); - } + GNUNET_assert (0 == json_array_append_new (inputs, jinput)); + } - GNUNET_assert (0 == json_array_append (outputs, joutput)); - } + for (unsigned int j = 0; j<choice->outputs_len; j++) + { + struct TALER_MerchantContractOutput *output = &choice->outputs[j]; - json_t *jchoice = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_incref ("inputs", - inputs), - GNUNET_JSON_pack_array_incref ("outputs", - outputs) + json_t *joutput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + output->type) ); - GNUNET_assert (0 == json_array_append (choices, jchoice)); + if (TALER_MCOT_TOKEN == output->type) + { + GNUNET_assert(0 == + json_object_set_new(joutput, + "number", + json_integer ( + output->details.token.count))); + + GNUNET_assert(0 == + json_object_set_new(joutput, + "token_family_slug", + json_string ( + output->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append (outputs, joutput)); } + + json_t *jchoice = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); + + GNUNET_assert (0 == json_array_append (choices, jchoice)); } oc->serialize_order.contract = GNUNET_JSON_PACK ( @@ -1613,6 +1640,9 @@ serialize_order (struct OrderContext *oc) GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", oc->parse_order.fulfillment_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + oc->parse_order.minimum_age)), GNUNET_JSON_pack_array_incref ("products", oc->merge_inventory.products), GNUNET_JSON_pack_data_auto ("h_wire", @@ -1635,8 +1665,8 @@ serialize_order (struct OrderContext *oc) 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_object_steal ("merchant", + merchant), GNUNET_JSON_pack_data_auto ("merchant_pub", &oc->hc->instance->merchant_pub), GNUNET_JSON_pack_array_incref ("exchanges", @@ -1883,6 +1913,10 @@ parse_order (struct OrderContext *oc) &oc->parse_order.delivery_date), NULL), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &oc->parse_order.minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_relative_time ("auto_refund", &oc->parse_order.auto_refund), NULL), @@ -2600,6 +2634,9 @@ merge_inventory (struct OrderContext *oc) ip->product_id); return; } + oc->parse_order.minimum_age + = GNUNET_MAX (oc->parse_order.minimum_age, + pd.minimum_age); { json_t *p; @@ -2778,9 +2815,22 @@ parse_request (struct OrderContext *oc) /* parse the inventory_products (optionally given) */ if (NULL != ip) { + unsigned int ipl = (unsigned int) json_array_size (ip); + + if ( (json_array_size (ip) != (size_t) ipl) || + (ipl > MAX_PRODUCTS) ) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "inventory products too long"); + return; + } GNUNET_array_grow (oc->parse_request.inventory_products, oc->parse_request.inventory_products_length, - json_array_size (ip)); + (unsigned int) json_array_size (ip)); for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++) { struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i]; |