merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 1527c39ab0ad79a923de5026afc772ac76e9a6b6
parent 0a164d09483349ff3f4a60d16b763593fdcdb178
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Wed, 11 Dec 2024 12:16:35 +0100

update of the conf to run tests

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 4+++-
Msrc/backend/taler-merchant-httpd_contract.c | 109++++++++++++++++++++++++++-----------------------------------------------------
Msrc/backend/taler-merchant-httpd_contract.h | 118+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 252+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/backend/taler-merchant-httpd_private-get-token-families-SLUG.c | 43++++++++++++++++++++++++++++++-------------
Msrc/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c | 32++++++++++++++++----------------
Msrc/backend/taler-merchant-httpd_private-post-instances.c | 5+++++
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 990+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/backend/taler-merchant-httpd_private-post-token-families.c | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/backenddb/Makefile.am | 1+
Msrc/backenddb/merchant-0008.sql | 8++++++--
Msrc/backenddb/merchant-0013.sql | 40+++++++++++++++++++++++++++++++++++++---
Msrc/backenddb/pg_insert_token_family.c | 29++++++++++++++++++-----------
Msrc/backenddb/pg_insert_token_family_key.c | 46+++++++++++++++++++++++++++++++---------------
Msrc/backenddb/pg_insert_token_family_key.h | 11+++++++----
Msrc/backenddb/pg_lookup_token_family.c | 26+++++++++++++++++++++-----
Msrc/backenddb/pg_lookup_token_family_key.c | 58++++++++++++++++++++++++++++++++++++----------------------
Msrc/backenddb/pg_lookup_token_family_key.h | 17+++++++++--------
Asrc/backenddb/pg_lookup_token_family_keys.c | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_token_family_keys.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/pg_update_token_family.c | 20+++++++++++---------
Msrc/backenddb/plugin_merchantdb_postgres.c | 3+++
Msrc/include/taler_merchant_service.h | 30++++++++++++++++++++++++++++--
Msrc/include/taler_merchantdb_plugin.h | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/lib/merchant_api_get_tokenfamily.c | 13++++++++++---
Msrc/lib/merchant_api_post_order_pay.c | 2++
Msrc/lib/merchant_api_post_tokenfamilies.c | 20++++++++++++++------
Msrc/testing/Makefile.am | 5+++--
Msrc/testing/test_merchant_api.c | 89++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/testing/test_merchant_api.conf | 1+
Msrc/testing/test_merchant_order_creation.sh | 14++++++++++----
Msrc/testing/testing_api_cmd_pay_order.c | 170+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/testing/testing_api_cmd_post_tokenfamilies.c | 9+++++++--
33 files changed, 1778 insertions(+), 951 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -1641,7 +1641,9 @@ url_handler (void *cls, if (MHD_NO == MHD_add_response_header (response, MHD_HTTP_HEADER_LOCATION, - rslash)) + NULL == rslash + ? "/" + : rslash)) { GNUNET_break (0); MHD_destroy_response (response); diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c @@ -22,6 +22,7 @@ #include <gnunet/gnunet_common.h> #include <jansson.h> #include <stdint.h> +#include <taler/taler_json_lib.h> #include "taler-merchant-httpd_contract.h" @@ -143,15 +144,15 @@ parse_choices (void *cls, size_t idx; json_array_foreach ((json_t *) jinputs, idx, jinput) { - struct TALER_MerchantContractInput input = {.details.token.count = 1}; + struct TALER_MerchantContractInput input = { + .details.token.count = 1 + }; const char *kind; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &input.details.token.token_family_slug), - GNUNET_JSON_spec_timestamp ("valid_after", - &input.details.token.valid_after), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &input.details.token.count), @@ -208,13 +209,14 @@ parse_choices (void *cls, .details.token.count = 1 }; const char *kind; + uint32_t ki; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &output.details.token.token_family_slug), - GNUNET_JSON_spec_timestamp ("valid_after", - &output.details.token.valid_after), + GNUNET_JSON_spec_uint32 ("key_index", + &ki), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &output.details.token.count), @@ -238,7 +240,7 @@ parse_choices (void *cls, GNUNET_break_op (0); return GNUNET_SYSERR; } - + output.details.token.key_index = ki; output.type = TMH_contract_output_type_from_string (kind); if (TALER_MCOT_INVALID == output.type) @@ -310,20 +312,14 @@ parse_token_families (void *cls, { const json_t *keys; struct TALER_MerchantContractTokenFamily family = { - .slug = slug + .slug = GNUNET_strdup (slug) }; - struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("keys", &keys), GNUNET_JSON_spec_bool ("critical", &family.critical), GNUNET_JSON_spec_end () - /* TODO: Figure out if these fields should be 'const' */ - // GNUNET_JSON_spec_string ("description", - // &family.description), - // GNUNET_JSON_spec_object_const ("description_i18n", - // &family.description_i18n), }; const char *error_name; unsigned int error_line; @@ -342,74 +338,40 @@ parse_token_families (void *cls, GNUNET_break_op (0); return GNUNET_SYSERR; } - GNUNET_array_grow (family.keys, family.keys_len, json_array_size (keys)); for (unsigned int i = 0; i<family.keys_len; i++) { - /* TODO: Move this to TALER_JSON_spec_token_issue_key */ - int64_t cipher; struct TALER_MerchantContractTokenFamilyKey *key = &family.keys[i]; - /* TODO: Free when not used anymore */ - - key->pub.public_key - = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); - { - struct GNUNET_JSON_Specification key_spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_pub", - &key->pub.public_key->pub_key_hash), - GNUNET_JSON_spec_rsa_public_key ( - "rsa_pub", - &key->pub.public_key->details.rsa_public_key), - // FIXME: sort out CS here! - // GNUNET_JSON_spec_fixed_auto ("cs_pub", - // &key.pub.public_key->details.cs_public_key)), - GNUNET_JSON_spec_int64 ( - "cipher", - &cipher), - GNUNET_JSON_spec_timestamp ( - "valid_after", - &key->valid_after), - GNUNET_JSON_spec_timestamp ( - "valid_before", - &key->valid_before), - GNUNET_JSON_spec_end () - }; - const char *ierror_name; - unsigned int ierror_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (keys, - i), - key_spec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - key_spec[ierror_line].field, - ierror_line, - ierror_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - - switch (cipher) + struct GNUNET_JSON_Specification key_spec[] = { + TALER_JSON_spec_token_pub ( + "public_key", + &key->pub), + GNUNET_JSON_spec_timestamp ( + "valid_after", + &key->valid_after), + GNUNET_JSON_spec_timestamp ( + "valid_before", + &key->valid_before), + GNUNET_JSON_spec_end () + }; + const char *ierror_name; + unsigned int ierror_line; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (keys, + i), + key_spec, + &ierror_name, + &ierror_line)) { - case GNUNET_CRYPTO_BSA_RSA: - key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA; - break; - case GNUNET_CRYPTO_BSA_CS: - key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_CS; - break; - case GNUNET_CRYPTO_BSA_INVALID: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'cipher' invalid in key #%u\n", - i); + "Failed to parse %s at %u: %s\n", + key_spec[ierror_line].field, + ierror_line, + ierror_name); GNUNET_break_op (0); return GNUNET_SYSERR; } @@ -418,7 +380,6 @@ parse_token_families (void *cls, *families_len, family); } - return GNUNET_OK; } @@ -477,5 +438,5 @@ TMH_find_token_family_key ( } /* no matching family found */ - return GNUNET_NO; + return GNUNET_SYSERR; } diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h @@ -18,6 +18,9 @@ * @brief shared logic for contract terms handling * @author Christian Blättler */ +#ifndef TALER_MERCHANT_HTTPD_CONTRACT_H +#define TALER_MERCHANT_HTTPD_CONTRACT_H + #include "taler-merchant-httpd.h" #include <gnunet/gnunet_common.h> #include <gnunet/gnunet_time_lib.h> @@ -93,8 +96,8 @@ struct TALER_MerchantContractInput union { /** - * Coin-based input (ration). (Future work, only here for reference) - */ + * Coin-based input (ration). (Future work, only here for reference) + */ // struct // { // /** @@ -109,25 +112,19 @@ struct TALER_MerchantContractInput // } coin; /** - * Token-based input. - */ + * Token-based input. + */ struct { /** - * Slug of the token family to be used. - */ + * Slug of the token family to be used. + */ const char *token_family_slug; /** - * Start time of the validity period of the token. Base on this timestamp - * the wallet can find the correct key for this token in token_authorities. - */ - struct GNUNET_TIME_Timestamp valid_after; - - /** - * Number of tokens of this type required. Defaults to one if the - * field is not provided. - */ + * Number of tokens of this type required. Defaults to one if the + * field is not provided. + */ unsigned int count; } token; } details; @@ -167,62 +164,66 @@ enum TALER_MerchantContractOutputType struct TALER_MerchantContractOutput { /** - * Type of the output. - */ + * Type of the output. + */ enum TALER_MerchantContractOutputType type; union { /** - * Coin-based output. - */ + * Coin-based output. + */ struct { /** - * Coins that will be yielded. This excludes any applicable withdraw fees. - */ + * Coins that will be yielded. This excludes any applicable withdraw fees. + */ struct TALER_Amount brutto_yield; /** - * Base URL of the exchange that will issue the coins. - */ + * Base URL of the exchange that will issue the coins. + */ const char *exchange_url; + } coin; /** - * Tax-receipt output. - */ + * Tax-receipt output. + */ struct { /** - * Base URL of the donation authority that will issue the tax receipt. - */ + * Base URL of the donation authority that will issue the tax receipt. + */ const char *donau_url; } tax_receipt; /** - * Token-based output. - */ + * Token-based output. + */ struct { /** - * Slug of the token family to be issued. - */ + * Slug of the token family to be issued. + */ const char *token_family_slug; /** - * Start time of the validity period of the token. Base on this timestamp - * the wallet can find the correct key for this token in token_authorities. - */ - struct GNUNET_TIME_Timestamp valid_after; + * Index of the public key in the @a token_family_slug's token family + * ``keys`` array that this output token will have. + */ + unsigned int key_index; /** - * Number of tokens of this type required. Defaults to one if the - * field is not provided. - */ + * Number of tokens of this type required. Defaults to one if the + * field is not provided. + */ unsigned int count; + } token; + } details; + }; /** @@ -264,12 +265,12 @@ struct TALER_MerchantContractTokenFamilyKey struct TALER_TokenIssuePublicKey pub; /** - * Tokens signed by this key will be valid after this time. + * Start time of the token family duration. */ struct GNUNET_TIME_Timestamp valid_after; /** - * Tokens signed by this key will be valid before this time. + * Tokens signed by this key will be valid until this time. */ struct GNUNET_TIME_Timestamp valid_before; }; @@ -279,7 +280,7 @@ struct TALER_MerchantContractTokenFamily /** * Slug of the token family. */ - const char *slug; + char *slug; /** * Human-readable name of the token family. @@ -334,12 +335,12 @@ struct TALER_MerchantContractTokenFamily * this type if the respective contract says so). May contain "*" for * any domain or subdomain. */ - const char **trusted_domains; + char **trusted_domains; /** * Length of the @e trusted_domains array. */ - unsigned int trusted_domains_len; + size_t trusted_domains_len; } subscription; /** @@ -356,7 +357,7 @@ struct TALER_MerchantContractTokenFamily * semantics (like get 20% discount for my competitors 30% discount * token). */ - const char **expected_domains; + char **expected_domains; /** * Length of the @e expected_domains array. @@ -594,29 +595,31 @@ TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract, * of contract choices. * * @param name name of the choices field in the JSON - * @param[out] choices pointer to the first element of the array - * @param[out] choices_len pointer to the length of the array + * @param[out] choices set to the first element of the array + * @param[out] choices_len set to the length of the @a choices array * @return spec for parsing a choices array */ struct GNUNET_JSON_Specification -TALER_JSON_spec_choices (const char *name, - struct TALER_MerchantContractChoice **choices, - unsigned int *choices_len); +TALER_JSON_spec_choices ( + const char *name, + struct TALER_MerchantContractChoice **choices, + unsigned int *choices_len); + /** * Provide specification to parse given JSON object to an array * of token families. * * @param name name of the token families field in the JSON - * @param[out] families pointer to the first element of the array - * @param[out] families_len pointer to the length of the array + * @param[out] families set to the first element of the array + * @param[out] families_len set to the length of the @a families * @return spec for parsing a token families object */ struct GNUNET_JSON_Specification -TALER_JSON_spec_token_families (const char *name, - struct TALER_MerchantContractTokenFamily ** - families, - unsigned int *families_len); +TALER_JSON_spec_token_families ( + const char *name, + struct TALER_MerchantContractTokenFamily **families, + unsigned int *families_len); /** @@ -624,13 +627,14 @@ TALER_JSON_spec_token_families (const char *name, * @a valid_after to find the matching public key within it. * * @param slug slug of the token family - * @param valid_after start time of the validity period of the key + * @param valid_after end time of the validity period of the key to find * @param families array of token families to search in * @param families_len length of the @a families array * @param[out] family found family, set to NULL to only check for existence * @param[out] key found key, set to NULL to only check for existence * @return #GNUNET_OK on success #GNUNET_NO if no key was found */ +// FIXME: awkward API, implementation duplicates code... enum GNUNET_GenericReturnValue TMH_find_token_family_key ( const char *slug, @@ -639,3 +643,5 @@ TMH_find_token_family_key ( unsigned int families_len, struct TALER_MerchantContractTokenFamily *family, struct TALER_MerchantContractTokenFamilyKey *key); + +#endif diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -2454,38 +2454,61 @@ phase_execute_pay_transaction (struct PayContext *pc) /** * Ensures that the expected number of tokens for a @e key * are provided as inputs and have valid signatures. + * + * @param[in,out] pc payment context we are processing + * @param family family the tokens should be from + * @param index number of the input we are handling + * @param expected_num number of tokens expected + * @return #GNUNET_YES on success */ static enum GNUNET_GenericReturnValue -find_valid_input_tokens (struct PayContext *pc, - struct TALER_MerchantContractTokenFamilyKey *key, - unsigned int index, - unsigned int expected_num) +find_valid_input_tokens ( + struct PayContext *pc, + const struct TALER_MerchantContractTokenFamily *family, + unsigned int index, + unsigned int expected_num) { unsigned int num_validated = 0; - struct TokenUseConfirmation *tuc = NULL; + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); for (unsigned int j = 0; j < expected_num; j++) { - tuc = &pc->tokens[index + j]; + struct TokenUseConfirmation *tuc = &pc->tokens[index + j]; + const struct TALER_MerchantContractTokenFamilyKey *key = NULL; - if (NULL == tuc) + for (unsigned int i=0; i<family->keys_len; i++) + { + const struct TALER_MerchantContractTokenFamilyKey *ki + = &family->keys[i]; + + if (GNUNET_TIME_timestamp_cmp (ki->valid_after, >, now) || + GNUNET_TIME_timestamp_cmp (ki->valid_before, <, now)) + { + continue; /* ki currently not valid */ + } + // FIXME: tuc->h_issue.hash is NOT initialized here! + if (0 == + GNUNET_memcmp (&ki->pub.public_key->pub_key_hash, + &tuc->h_issue.hash)) + { + key = ki; + break; + } + } + if (NULL == key) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Input token for public key with " - "valid_after `%s' not found\n", - GNUNET_TIME_timestamp2s (key->valid_after)); + "Input token supplied for public key that is not acceptable\n"); GNUNET_break (0); pay_end (pc, TALER_MHD_reply_with_error ( pc->connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "'tokens' array is missing required input token")); + TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN, + NULL)); return GNUNET_NO; } - - tuc->h_issue.hash = key->pub.public_key->pub_key_hash; - if (GNUNET_OK != TALER_token_issue_verify (&tuc->pub, &key->pub, @@ -2512,9 +2535,9 @@ find_valid_input_tokens (struct PayContext *pc, &tuc->sig)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Input token for public key with valid_after " + "Input token for public key with valid_before " "`%s' has invalid use signature\n", - GNUNET_TIME_timestamp2s (key->valid_after)); + GNUNET_TIME_timestamp2s (key->valid_before)); GNUNET_break (0); pay_end (pc, TALER_MHD_reply_with_error ( @@ -2531,10 +2554,9 @@ find_valid_input_tokens (struct PayContext *pc, if (num_validated != expected_num) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Expected %d tokens for public key with valid_after " - "`%s', but found %d\n", + "Expected %d tokens for family %s, but found %d\n", expected_num, - GNUNET_TIME_timestamp2s (key->valid_after), + family->slug, num_validated); GNUNET_break (0); pay_end (pc, @@ -2616,6 +2638,34 @@ sign_token_envelopes (struct PayContext *pc, /** + * Find the family entry for the family of the given @a slug + * in @a pc. + * + * @param[in] pc payment context to search + * @param slug slug to search for + * @return NULL if @a slug was not found + */ +static struct TALER_MerchantContractTokenFamily * +find_family (const struct PayContext *pc, + const char *slug) +{ + for (unsigned int i = 0; i<pc->token_families_len; i++) + { + if (0 == strcmp (pc->token_families[i].slug, + slug)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Token family %s found with %u keys\n", + slug, + pc->token_families[i].keys_len); + return &pc->token_families[i]; + } + } + return NULL; +} + + +/** * Validate tokens and token envelopes. First, we check if all tokens listed * in the 'inputs' array of the selected choice are present in the 'tokens' * array of the request. Then, we validate the signatures of each provided @@ -2668,33 +2718,24 @@ phase_validate_tokens (struct PayContext *pc) } { - struct TALER_MerchantContractChoice selected; - struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); - - selected = pc->choices[pc->choice_index]; + const struct TALER_MerchantContractChoice *selected + = &pc->choices[pc->choice_index]; - for (unsigned int i = 0; i<selected.inputs_len; i++) + for (unsigned int i = 0; i<selected->inputs_len; i++) { - struct TALER_MerchantContractInput input = selected.inputs[i]; - struct TALER_MerchantContractTokenFamily family; - struct TALER_MerchantContractTokenFamilyKey key; + const struct TALER_MerchantContractInput *input + = &selected->inputs[i]; + const struct TALER_MerchantContractTokenFamily *family; - if (input.type != TALER_MCIT_TOKEN) + if (input->type != TALER_MCIT_TOKEN) { /* only validate inputs of type token (for now) */ continue; } - /* TODO: Replace this with ordering convention. */ - if (GNUNET_OK != - TMH_find_token_family_key (input.details.token. - token_family_slug, - input.details.token. - valid_after, - pc->token_families, - pc->token_families_len, - &family, - &key)) + family = find_family (pc, + input->details.token.token_family_slug); + if (NULL == family) { /* this should never happen, since the choices and token families are validated on insert. */ @@ -2704,32 +2745,14 @@ phase_validate_tokens (struct PayContext *pc) pc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL)); - return; - } - - /* Ensure tokens signed by this key are valid at the current time. */ - if (GNUNET_TIME_timestamp_cmp (key.valid_after, >, now) || - GNUNET_TIME_timestamp_cmp (key.valid_before, <=, now)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Token family key validity period from %s to %s " - "is not valid at the current time\n", - GNUNET_TIME_timestamp2s (key.valid_after), - GNUNET_TIME_timestamp2s (key.valid_before)); - GNUNET_break (0); - pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID, - NULL)); + "token family not found in order")); return; } - - if (GNUNET_NO == find_valid_input_tokens (pc, - &key, - i, - input.details.token.count)) + if (GNUNET_NO == + find_valid_input_tokens (pc, + family, + i, + input->details.token.count)) { /* Error is already scheduled from find_valid_input_token. */ return; @@ -2738,58 +2761,74 @@ phase_validate_tokens (struct PayContext *pc) GNUNET_array_grow (pc->output_tokens, pc->output_tokens_len, - selected.outputs_len); + selected->outputs_len); - for (unsigned int i = 0; i<selected.outputs_len; i++) + for (unsigned int i = 0; i<selected->outputs_len; i++) { enum GNUNET_DB_QueryStatus qs; struct TALER_MERCHANTDB_TokenFamilyKeyDetails details; - struct TALER_MerchantContractOutput output = selected.outputs[i]; - struct TALER_MerchantContractTokenFamily family; - struct TALER_MerchantContractTokenFamilyKey key; + const struct TALER_MerchantContractOutput *output + = &selected->outputs[i]; + struct TALER_MerchantContractTokenFamily *family; + struct TALER_MerchantContractTokenFamilyKey *key; - if (output.type != TALER_MCOT_TOKEN) + if (output->type != TALER_MCOT_TOKEN) { /* only validate outputs of type tokens (for now) */ continue; } - if (GNUNET_OK != - TMH_find_token_family_key (output.details.token. - token_family_slug, - output.details.token. - valid_after, - pc->token_families, - pc->token_families_len, - &family, - &key)) + family = find_family (pc, + output->details.token.token_family_slug); + if (NULL == family) { /* this should never happen, since the choices and token families are validated on insert. */ GNUNET_break (0); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL)); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "token family not found in order")); return; } - - qs = TMH_db->lookup_token_family_key (TMH_db->cls, - pc->hc->instance->settings.id, - family.slug, - key.valid_after, - key.valid_after, - &details); - + if (output->details.token.key_index >= family->keys_len) + { + /* this should never happen, since the choices and + token families are validated on insert. */ + GNUNET_break (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "key index invalid for token family")); + return; + } + key = &family->keys[output->details.token.key_index]; + qs = TMH_db->lookup_token_family_key ( + TMH_db->cls, + pc->hc->instance->settings.id, + family->slug, + pc->timestamp, + pc->pay_deadline, + &details); if (qs <= 0) { + GNUNET_log ( + GNUNET_ERROR_TYPE_ERROR, + "Did not find key for %s at [%llu,%llu]\n", + family->slug, + (unsigned long long) pc->timestamp.abs_time.abs_value_us, + (unsigned long long) pc->pay_deadline.abs_time.abs_value_us); GNUNET_break (0); pay_end (pc, - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL)); + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL)); return; } @@ -2797,13 +2836,13 @@ phase_validate_tokens (struct PayContext *pc) if (GNUNET_OK != sign_token_envelopes (pc, - &key, + key, &details.priv, /* TODO: Use critical field stored in database here instead. */ details.token_family.kind == TALER_MERCHANTDB_TFK_Subscription, i, - output.details.token.count)) + output->details.token.count)) { /* Error is already scheduled from sign_token_envelopes. */ return; @@ -3632,6 +3671,8 @@ phase_parse_pay (struct PayContext *pc) &tuc->sig), GNUNET_JSON_spec_fixed_auto ("token_pub", &tuc->pub), + GNUNET_JSON_spec_fixed_auto ("h_issue", + &tuc->h_issue), TALER_JSON_spec_token_issue_sig ("ub_sig", &tuc->unblinded_sig), GNUNET_JSON_spec_end () @@ -3710,6 +3751,25 @@ pay_context_cleanup (void *cls) GNUNET_free (eg); } GNUNET_free (pc->egs); + for (unsigned int i = 0; i< pc->token_families_len; i++) + { + struct TALER_MerchantContractTokenFamily *tf + = &pc->token_families[i]; + + GNUNET_free (tf->slug); + GNUNET_free (tf->name); + GNUNET_free (tf->description); + json_decref (tf->description_i18n); + for (unsigned int j = 0; j<tf->keys_len; j++) + { + struct TALER_MerchantContractTokenFamilyKey *key + = &tf->keys[j]; + + TALER_token_issue_pub_free (&key->pub); + } + GNUNET_free (tf->keys); + } + GNUNET_free (pc->token_families); if (NULL != pc->response) { MHD_destroy_response (pc->response); diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2023 Taler Systems SA + (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -54,11 +54,11 @@ TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_DB_FETCH_FAILED, "lookup_token_family"); } - if (0 == status) + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == status) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, - TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, + TALER_EC_MERCHANT_GENERIC_TOKEN_FAMILY_UNKNOWN, hc->infix); } { @@ -85,25 +85,42 @@ TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh, result = TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - GNUNET_JSON_pack_string ("slug", details.slug), - GNUNET_JSON_pack_string ("name", details.name), - GNUNET_JSON_pack_string ("description", details.description), + GNUNET_JSON_pack_string ("slug", + details.slug), + GNUNET_JSON_pack_string ("name", + details.name), + GNUNET_JSON_pack_string ("description", + details.description), GNUNET_JSON_pack_object_steal ("description_i18n", details.description_i18n), - GNUNET_JSON_pack_timestamp ("valid_after", details.valid_after), - GNUNET_JSON_pack_timestamp ("valid_before", details.valid_before), - GNUNET_JSON_pack_time_rel ("duration", details.duration), - GNUNET_JSON_pack_string ("kind", kind), - GNUNET_JSON_pack_int64 ("issued", details.issued), - GNUNET_JSON_pack_int64 ("used", details.used) + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("extra_data", + details.extra_data)), + GNUNET_JSON_pack_timestamp ("valid_after", + details.valid_after), + GNUNET_JSON_pack_timestamp ("valid_before", + details.valid_before), + GNUNET_JSON_pack_time_rel ("duration", + details.duration), + GNUNET_JSON_pack_time_rel ("validity_granularity", + details.validity_granularity), + GNUNET_JSON_pack_time_rel ("start_offset", + details.start_offset), + GNUNET_JSON_pack_string ("kind", + kind), + GNUNET_JSON_pack_int64 ("issued", + details.issued), + GNUNET_JSON_pack_int64 ("used", + details.used) ); GNUNET_free (details.name); GNUNET_free (details.description); + GNUNET_free (details.cipher_spec); GNUNET_free (kind); return result; } } -/* end of taler-merchant-httpd_private-get-products-SLUG.c */ +/* end of taler-merchant-httpd_private-get-token-families-SLUG.c */ diff --git a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2023 Taler Systems SA + (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -33,6 +33,7 @@ */ #define MAX_RETRIES 3 + /** * Handle a PATCH "/tokenfamilies/$slug" request. * @@ -49,7 +50,6 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, struct TMH_MerchantInstance *mi = hc->instance; const char *slug = hc->infix; struct TALER_MERCHANTDB_TokenFamilyDetails details = {0}; - enum GNUNET_DB_QueryStatus qs; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", (const char **) &details.name), @@ -59,15 +59,16 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, GNUNET_JSON_spec_json ("description_i18n", &details.description_i18n), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("extra_data", + &details.extra_data), + NULL), GNUNET_JSON_spec_timestamp ("valid_after", &details.valid_after), GNUNET_JSON_spec_timestamp ("valid_before", &details.valid_before), - GNUNET_JSON_spec_relative_time ("duration", - &details.duration), GNUNET_JSON_spec_end () }; - struct GNUNET_TIME_Relative validity; GNUNET_assert (NULL != mi); GNUNET_assert (NULL != slug); @@ -83,19 +84,17 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, : MHD_NO; } - validity = GNUNET_TIME_absolute_get_difference ( - details.valid_after.abs_time, - details.valid_before.abs_time); - - /* Check if start_time is before valid_before */ - if (GNUNET_TIME_relative_is_zero (validity)) + /* Ensure that valid_after is before valid_before */ + if (GNUNET_TIME_timestamp_cmp (details.valid_after, + >=, + details.valid_before)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid_validity_duration"); + "valid_after >= valid_before"); } if (NULL == details.description_i18n) @@ -113,13 +112,14 @@ TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh, "description_i18n"); } - qs = TMH_db->update_token_family (TMH_db->cls, - mi->settings.id, - slug, - &details); { + enum GNUNET_DB_QueryStatus qs; MHD_RESULT ret = MHD_NO; + qs = TMH_db->update_token_family (TMH_db->cls, + mi->settings.id, + slug, + &details); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c b/src/backend/taler-merchant-httpd_private-post-instances.c @@ -293,6 +293,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, { mi->rc = 1; TMH_instance_decref (mi); + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_START_FAILED, @@ -317,6 +318,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, is.id); mi->rc = 1; TMH_instance_decref (mi); + GNUNET_JSON_parse_free (spec); return ret; } case GNUNET_DB_STATUS_SOFT_ERROR: @@ -333,6 +335,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh, is.id); mi->rc = 1; TMH_instance_decref (mi); + GNUNET_JSON_parse_free (spec); return ret; } case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: @@ -350,6 +353,7 @@ retry: { mi->rc = 1; TMH_instance_decref (mi); + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, @@ -367,6 +371,7 @@ retry: created */ } + GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_static (connection, MHD_HTTP_NO_CONTENT, NULL, diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -255,22 +255,22 @@ struct OrderContext /** * Our order ID. */ - const char *order_id; + char *order_id; /** - * Summary of the contract. - */ + * Summary of the contract. + */ const char *summary; /** - * Internationalized summary. - */ - json_t *summary_i18n; + * Internationalized summary. + */ + const json_t *summary_i18n; /** - * URL that will show that the contract was successful - * after it has been paid for. - */ + * URL that will show that the contract was successful + * after it has been paid for. + */ const char *fulfillment_url; /** @@ -280,9 +280,9 @@ struct OrderContext const char *fulfillment_message; /** - * Map from IETF BCP 47 language tags to localized fulfillment messages. - */ - json_t *fulfillment_message_i18n; + * Map from IETF BCP 47 language tags to localized fulfillment messages. + */ + const json_t *fulfillment_message_i18n; /** * Array of products that are part of the purchase. @@ -332,7 +332,7 @@ struct OrderContext /** * Delivery location. */ - json_t *delivery_location; + const json_t *delivery_location; /** * Gross amount value of the contract. Used to @@ -693,21 +693,6 @@ clean_order (void *cls) json_decref (oc->set_exchanges.exchanges); oc->set_exchanges.exchanges = NULL; } - if (NULL != oc->parse_order.fulfillment_message_i18n) - { - json_decref (oc->parse_order.fulfillment_message_i18n); - oc->parse_order.fulfillment_message_i18n = NULL; - } - if (NULL != oc->parse_order.summary_i18n) - { - json_decref (oc->parse_order.summary_i18n); - oc->parse_order.summary_i18n = NULL; - } - if (NULL != oc->parse_order.delivery_location) - { - json_decref (oc->parse_order.delivery_location); - oc->parse_order.delivery_location = NULL; - } if (NULL != oc->merge_inventory.products) { json_decref (oc->merge_inventory.products); @@ -727,17 +712,32 @@ clean_order (void *cls) 0); for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++) { - GNUNET_free (oc->parse_choices.token_families[i].name); - GNUNET_free (oc->parse_choices.token_families[i].description); - json_decref (oc->parse_choices.token_families[i].description_i18n); - for (unsigned int j = 0; j<oc->parse_choices.token_families[i].keys_len; j++ - ) + struct TALER_MerchantContractTokenFamily *mctf + = &oc->parse_choices.token_families[i]; + + GNUNET_free (mctf->slug); + GNUNET_free (mctf->name); + GNUNET_free (mctf->description); + json_decref (mctf->description_i18n); + switch (mctf->kind) + { + case TALER_MCTK_SUBSCRIPTION: + for (size_t j = 0; j<mctf->details.subscription.trusted_domains_len; j++) + GNUNET_free (mctf->details.subscription.trusted_domains[j]); + GNUNET_free (mctf->details.subscription.trusted_domains); + break; + case TALER_MCTK_DISCOUNT: + for (size_t j = 0; j<mctf->details.discount.expected_domains_len; j++) + GNUNET_free (mctf->details.discount.expected_domains[j]); + GNUNET_free (mctf->details.discount.expected_domains); + break; + } + for (unsigned int j = 0; j<mctf->keys_len; j++) { - GNUNET_CRYPTO_blind_sign_pub_decref (oc->parse_choices.token_families[i]. - keys[j].pub.public_key); + GNUNET_CRYPTO_blind_sign_pub_decref (mctf->keys[j].pub.public_key); } - GNUNET_array_grow (oc->parse_choices.token_families[i].keys, - oc->parse_choices.token_families[i].keys_len, + GNUNET_array_grow (mctf->keys, + mctf->keys_len, 0); } GNUNET_array_grow (oc->parse_choices.token_families, @@ -751,6 +751,7 @@ clean_order (void *cls) 0); json_decref (oc->parse_request.order); json_decref (oc->serialize_order.contract); + GNUNET_free (oc->parse_order.order_id); GNUNET_free (oc->parse_order.merchant_base_url); GNUNET_free (oc); } @@ -1202,376 +1203,589 @@ salt_forgettable (struct OrderContext *oc) /** * Get rounded time interval. @a start is calculated by rounding - * @a ts down to the nearest multiple of @a precision. @a end is - * the next higher multiple of @a precision. + * @a ts down to the nearest multiple of @a precision. * * @param precision rounding precision. * year, month, day, hour, minute are supported. * @param ts timestamp to round * @param[out] start start of the interval - * @param[out] end end of the interval * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue get_rounded_time_interval (struct GNUNET_TIME_Relative precision, struct GNUNET_TIME_Timestamp ts, - struct GNUNET_TIME_Timestamp *start, - struct GNUNET_TIME_Timestamp *end) + struct GNUNET_TIME_Timestamp *start) { - struct tm*timeinfo; + struct tm timeinfo; time_t seconds; seconds = GNUNET_TIME_timestamp_to_s (ts); - timeinfo = localtime (&seconds); + GNUNET_break (NULL != + localtime_r (&seconds, + &timeinfo)); - if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision)) + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, + ==, + precision)) { - timeinfo->tm_mon = 0; - timeinfo->tm_mday = 1; - timeinfo->tm_hour = 0; - timeinfo->tm_min = 0; - timeinfo->tm_sec = 0; + timeinfo.tm_mon = 0; + timeinfo.tm_mday = 1; + timeinfo.tm_hour = 0; + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision)) + else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, + ==, + precision)) { - timeinfo->tm_mday = 1; - timeinfo->tm_hour = 0; - timeinfo->tm_min = 0; - timeinfo->tm_sec = 0; + timeinfo.tm_mday = 1; + timeinfo.tm_hour = 0; + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision)) + else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, + ==, + precision)) { - timeinfo->tm_hour = 0; - timeinfo->tm_min = 0; - timeinfo->tm_sec = 0; + timeinfo.tm_hour = 0; + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision)) + else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, + ==, + precision)) { - timeinfo->tm_min = 0; - timeinfo->tm_sec = 0; + timeinfo.tm_min = 0; + timeinfo.tm_sec = 0; } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision)) + else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, + ==, + precision)) { - timeinfo->tm_sec = 0; + timeinfo.tm_sec = 0; } else { return GNUNET_SYSERR; } + seconds = mktime (&timeinfo); + GNUNET_break (seconds != (time_t) -1); + *start = GNUNET_TIME_timestamp_from_s (seconds); + return GNUNET_OK; +} - *start = GNUNET_TIME_timestamp_from_s (mktime (timeinfo)); - if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision)) - { - timeinfo->tm_year++; - } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision)) - { - timeinfo->tm_mon = (timeinfo->tm_mon + 1) % 12; - } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision)) +/** + * Find the family entry for the family of the given @a slug + * in @a oc. + * + * @param[in] oc order context to search + * @param slug slug to search for + * @return NULL if @a slug was not found + */ +static struct TALER_MerchantContractTokenFamily * +find_family (const struct OrderContext *oc, + const char *slug) +{ + for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++) { - timeinfo->tm_mday++; + if (0 == strcmp (oc->parse_choices.token_families[i].slug, + slug)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Token family %s already in order\n", + slug); + return &oc->parse_choices.token_families[i]; + } } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision)) - { - timeinfo->tm_hour++; + return NULL; +} + + +/** + * Function called with each applicable family key that should + * be added to the respective token family of the order. + * + * @param cls a `struct OrderContext *` to expand + * @param tfkd token family key details to add to the contract + */ +static void +add_family_key (void *cls, + const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *tfkd) +{ + struct OrderContext *oc = cls; + const struct TALER_MERCHANTDB_TokenFamilyDetails *tf = &tfkd->token_family; + struct TALER_MerchantContractTokenFamily *family; + + family = find_family (oc, + tf->slug); + if (NULL == family) + { + /* Family not yet in our contract terms, create new entry */ + struct TALER_MerchantContractTokenFamily new_family = { + .slug = GNUNET_strdup (tf->slug), + .name = GNUNET_strdup (tf->name), + .description = GNUNET_strdup (tf->description), + .description_i18n = json_incref (tf->description_i18n), + }; + + switch (tf->kind) + { + case TALER_MERCHANTDB_TFK_Subscription: + { + json_t *tdomains = json_object_get (tf->extra_data, + "trusted_domains"); + json_t *dom; + size_t i; + + new_family.kind = TALER_MCTK_SUBSCRIPTION; + new_family.critical = true; + new_family.details.subscription.trusted_domains_len + = json_array_size (tdomains); + GNUNET_assert (new_family.details.subscription.trusted_domains_len + < UINT_MAX); + new_family.details.subscription.trusted_domains + = GNUNET_new_array ( + new_family.details.subscription.trusted_domains_len, + char *); + json_array_foreach (tdomains, i, dom) + { + const char *val; + + val = json_string_value (dom); + GNUNET_break (NULL != val); + if (NULL != val) + new_family.details.subscription.trusted_domains[i] + = GNUNET_strdup (val); + } + break; + } + case TALER_MERCHANTDB_TFK_Discount: + { + json_t *edomains = json_object_get (tf->extra_data, + "expected_domains"); + json_t *dom; + size_t i; + + new_family.kind = TALER_MCTK_DISCOUNT; + new_family.critical = false; + new_family.details.discount.expected_domains_len + = json_array_size (edomains); + GNUNET_assert (new_family.details.discount.expected_domains_len + < UINT_MAX); + new_family.details.discount.expected_domains + = GNUNET_new_array ( + new_family.details.discount.expected_domains_len, + char *); + json_array_foreach (edomains, i, dom) + { + const char *val; + + val = json_string_value (dom); + GNUNET_break (NULL != val); + if (NULL != val) + new_family.details.discount.expected_domains[i] + = GNUNET_strdup (val); + } + break; + } + } + GNUNET_array_append (oc->parse_choices.token_families, + oc->parse_choices.token_families_len, + new_family); + family = &oc->parse_choices.token_families[ + oc->parse_choices.token_families_len - 1]; } - else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision)) + if (NULL == tfkd->pub.public_key) + return; + for (unsigned int i = 0; i<family->keys_len; i++) { - timeinfo->tm_min++; + if (TALER_token_issue_pub_cmp (&family->keys[i].pub, + &tfkd->pub)) + { + /* A matching key is already in the list. */ + return; + } } - else + { - return GNUNET_SYSERR; - } + struct TALER_MerchantContractTokenFamilyKey key; - *end = GNUNET_TIME_timestamp_from_s (mktime (timeinfo)); - return GNUNET_OK; + TALER_token_issue_pub_copy (&key.pub, + &tfkd->pub); + key.valid_after = tfkd->signature_validity_start; + key.valid_before = tfkd->signature_validity_end; + GNUNET_array_append (family->keys, + family->keys_len, + key); + } } /** - * Check if the token family with the given @a slug is already present in - * the list of token families for this order. If not, fetch its details and - * add it to the list. Then check if there is a public key with a matching - * @a valid_after field. If not, generate a new key pair and store it in the - * database. + * Check if the token family with the given @a slug is already present in the + * list of token families for this order. If not, fetch its details and add it + * to the list. * * @param[in,out] oc order context * @param slug slug of the token family - * @param[in,out] valid_after validity start date of the token, - subject to rounding. Set to the rounded validity - start date of the matching key. * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -set_token_family (struct OrderContext *oc, - const char *slug, - struct GNUNET_TIME_Timestamp *valid_after) +add_input_token_family (struct OrderContext *oc, + const char *slug) { - struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details; - struct TALER_MerchantContractTokenFamily *family = NULL; + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + struct GNUNET_TIME_Timestamp end = oc->parse_order.pay_deadline; enum GNUNET_DB_QueryStatus qs; - /* TODO: Implement rounding duration of token family and use this here. */ - struct GNUNET_TIME_Relative precision = GNUNET_TIME_UNIT_MONTHS; - struct GNUNET_TIME_Timestamp min_valid_after; - struct GNUNET_TIME_Timestamp max_valid_after; - - if (GNUNET_OK != - get_rounded_time_interval (precision, - *valid_after, - &min_valid_after, - &max_valid_after)) - { + enum TALER_ErrorCode ec; + unsigned int http_status; + + qs = TMH_db->lookup_token_family_keys (TMH_db->cls, + oc->hc->instance->settings.id, + slug, + now, + end, + &add_family_key, + oc); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); - reply_with_error (oc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "valid_after"); - return GNUNET_SYSERR; - } + 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, + "Input token family slug %s unknown\n", + slug); + http_status = MHD_HTTP_NOT_FOUND; + ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + return GNUNET_OK; + } + reply_with_error (oc, + http_status, + ec, + slug); + return GNUNET_SYSERR; +} - for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++) + +/** + * Find the index of a key in the @a family that is valid at + * the time @a valid_at. + * + * @param family to search + * @param valid_at time when the key must be valid + * @param[out] key_index index to initialize + * @return #GNUNET_OK if a matching key was found + */ +static enum GNUNET_GenericReturnValue +find_key_index (struct TALER_MerchantContractTokenFamily *family, + struct GNUNET_TIME_Timestamp valid_at, + unsigned int *key_index) +{ + for (unsigned int i = 0; i<family->keys_len; i++) { - if (0 == strcmp (oc->parse_choices.token_families[i].slug, - slug)) + if ( (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after, + <=, + valid_at)) && + (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_before, + >=, + valid_at)) ) { - family = &oc->parse_choices.token_families[i]; - break; + /* The token family and a matching key already exist. */ + *key_index = i; + return GNUNET_OK; } } - if (NULL != family) + return GNUNET_NO; +} + + +/** + * Create fresh key pair based on @a cipher_spec. + * + * @param cipher_spec which kind of key pair should we generate + * @param[out] priv set to new private key + * @param[out] pub set to new public key + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +create_key (const char *cipher_spec, + struct TALER_TokenIssuePrivateKey *priv, + struct TALER_TokenIssuePublicKey *pub) +{ + unsigned int len; + char dummy; + + if (0 == strcmp ("cs", + cipher_spec)) { - for (unsigned int i = 0; i<family->keys_len; i++) - { - if (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after, - >=, - min_valid_after) && - GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after, - <, - max_valid_after)) - { - /* The token family and a matching key is already added. */ - *valid_after = family->keys[i].valid_after; - return GNUNET_OK; - } - } + GNUNET_CRYPTO_blind_sign_keys_create ( + &priv->private_key, + &pub->public_key, + GNUNET_CRYPTO_BSA_CS); + return GNUNET_OK; } + if (1 == + sscanf (cipher_spec, + "rsa(%u)%c", + &len, + &dummy)) + { + GNUNET_CRYPTO_blind_sign_keys_create ( + &priv->private_key, + &pub->public_key, + GNUNET_CRYPTO_BSA_RSA, + len); + return GNUNET_OK; + } + return GNUNET_SYSERR; +} + +/** + * Check if the token family with the given @a slug is already present in the + * list of token families for this order. If not, fetch its details and add it + * to the list. Also checks if there is a public key with that expires after + * the payment deadline. If not, generates a new key pair and stores it in + * the database. + * + * @param[in,out] oc order context + * @param slug slug of the token family + * @param valid_at time when the token returned must be valid + * @param[out] key_index set to the index of the respective public + * key in the @a slug's token family keys array. + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +add_output_token_family (struct OrderContext *oc, + const char *slug, + struct GNUNET_TIME_Timestamp valid_at, + unsigned int *key_index) +{ + struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details; + struct TALER_MerchantContractTokenFamily *family; + enum GNUNET_DB_QueryStatus qs; + + family = find_family (oc, + slug); + if ( (NULL != family) && + (GNUNET_OK == + find_key_index (family, + valid_at, + key_index)) ) + return GNUNET_OK; qs = TMH_db->lookup_token_family_key (TMH_db->cls, oc->hc->instance->settings.id, slug, - min_valid_after, - max_valid_after, + valid_at, + oc->parse_order.pay_deadline, &key_details); - /* slug is not needed */ - GNUNET_free (key_details.token_family.slug); - - if (qs <= 0) + switch (qs) { - 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 slug %s unknown\n", - slug); - http_status = MHD_HTTP_NOT_FOUND; - ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN; - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* case listed to make compilers happy */ - GNUNET_assert (0); - } + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_token_family_key"); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); reply_with_error (oc, - http_status, - ec, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "lookup_token_family_key"); + return GNUNET_SYSERR; /* FIXME: retry instead? */ + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Output token family slug %s unknown\n", + slug); + reply_with_error (oc, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN, slug); return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Lookup of token family %s at %llu yielded %s\n", + slug, + (unsigned long long) valid_at.abs_time.abs_value_us, + NULL == key_details.pub.public_key ? "no key" : "a key"); + + if (NULL == family) { - struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + add_family_key (oc, + &key_details); + family = find_family (oc, + slug); + GNUNET_assert (NULL != family); + } + /* we don't need the full family details anymore */ + GNUNET_free (key_details.token_family.slug); + GNUNET_free (key_details.token_family.name); + GNUNET_free (key_details.token_family.description); + json_decref (key_details.token_family.description_i18n); + json_decref (key_details.token_family.extra_data); + + if (NULL != key_details.pub.public_key) + { + /* lookup_token_family_key must have found a matching key, + and it must have been added. Find and use the index. */ + GNUNET_CRYPTO_blind_sign_pub_decref (key_details.pub.public_key); + GNUNET_CRYPTO_blind_sign_priv_decref (key_details.priv.private_key); + GNUNET_free (key_details.token_family.cipher_spec); + GNUNET_assert (GNUNET_OK == + find_key_index (family, + valid_at, + key_index)); + return GNUNET_OK; + } - /* Verify that the token family is valid right now. */ - if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after, - >, - now) || - GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before, - <=, - now)) + /* No suitable key exists, create one! */ + { + struct TALER_MerchantContractTokenFamilyKey key; + enum GNUNET_DB_QueryStatus iqs; + struct TALER_TokenIssuePrivateKey token_priv; + struct GNUNET_TIME_Timestamp key_expires; + struct GNUNET_TIME_Timestamp round_start; + + if (GNUNET_OK != + get_rounded_time_interval ( + key_details.token_family.validity_granularity, + valid_at, + &round_start)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Token family %s expired or not yet valid\n", + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unsupported validity granularity interval %s found in database for token family %s!\n", + GNUNET_TIME_relative2s ( + key_details.token_family.validity_granularity, + false), slug); + GNUNET_free (key_details.token_family.cipher_spec); reply_with_error (oc, - /* TODO: HTTP Status Code GONE would be more elegant, - but that is already used to indicate that a product is out of stock. */ - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID, - slug); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "get_rounded_time_interval failed"); return GNUNET_SYSERR; } - } - - { - struct TALER_MerchantContractTokenFamilyKey key; - - if (NULL == family) + if (GNUNET_TIME_relative_cmp ( + key_details.token_family.duration, + <, + GNUNET_TIME_relative_add ( + key_details.token_family.validity_granularity, + key_details.token_family.start_offset))) { - struct TALER_MerchantContractTokenFamily new_family = { - .slug = slug, - .name = key_details.token_family.name, - .description = key_details.token_family.description, - .description_i18n = key_details.token_family.description_i18n, - .keys = GNUNET_new (struct TALER_MerchantContractTokenFamilyKey), - .keys_len = 0, - }; - - switch (key_details.token_family.kind) - { - case TALER_MERCHANTDB_TFK_Subscription: - new_family.kind = TALER_MCTK_SUBSCRIPTION; - new_family.critical = true; - // TODO: Set trusted domains - break; - case TALER_MERCHANTDB_TFK_Discount: - new_family.kind = TALER_MCTK_DISCOUNT; - new_family.critical = false; - // TODO: Set expected domains - break; - } - - GNUNET_array_append (oc->parse_choices.token_families, - oc->parse_choices.token_families_len, - new_family); - - family = &oc->parse_choices.token_families[oc->parse_choices. - token_families_len - 1]; + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Inconsistent duration %s found in database for token family %s (below validity granularity plus start_offset)!\n", + GNUNET_TIME_relative2s (key_details.token_family.duration, + false), + slug); + GNUNET_free (key_details.token_family.cipher_spec); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "duration, validty_granularity and start_offset inconsistent for token family"); + return GNUNET_SYSERR; } - - if (NULL == key_details.pub.public_key) + key.valid_after + = GNUNET_TIME_timestamp_max ( + GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract ( + round_start.abs_time, + key_details.token_family.start_offset)), + key_details.token_family.valid_after); + key.valid_before + = GNUNET_TIME_timestamp_min ( + GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add ( + key.valid_after.abs_time, + key_details.token_family.duration)), + key_details.token_family.valid_before); + GNUNET_assert (GNUNET_OK == + get_rounded_time_interval ( + key_details.token_family.validity_granularity, + key.valid_before, + &key_expires)); + GNUNET_assert (GNUNET_TIME_timestamp_cmp ( + key_expires, + !=, + round_start)); + if (GNUNET_OK != + create_key (key_details.token_family.cipher_spec, + &token_priv, + &key.pub)) { - /* There is no matching key for this token family yet. */ - /* We have to generate a fresh key pair. */ - /* If public key is NULL, private key must also be NULL */ - enum GNUNET_DB_QueryStatus iqs; - struct GNUNET_CRYPTO_BlindSignPrivateKey *priv; - struct GNUNET_CRYPTO_BlindSignPublicKey *pub; - struct GNUNET_TIME_Timestamp valid_before = - GNUNET_TIME_absolute_to_timestamp ( - GNUNET_TIME_absolute_add (min_valid_after.abs_time, - key_details.token_family.duration)); - - GNUNET_assert (NULL == key_details.priv.private_key); - if (GNUNET_TIME_timestamp_cmp (min_valid_after, - <, - key_details.token_family.valid_after)) - { - /* If key would start before validity of token family, - set valid_after of key to the value of the token family. */ - min_valid_after = key_details.token_family.valid_after; - } - - if (GNUNET_TIME_timestamp_cmp (valid_before, - >, - key_details.token_family.valid_before)) - { - /* If key would end after validity of token family, - set valid_before of key to the value of the token family. */ - valid_before = key_details.token_family.valid_before; - } - - GNUNET_CRYPTO_blind_sign_keys_create (&priv, - &pub, - /* TODO: Make cipher and key length configurable */ - GNUNET_CRYPTO_BSA_RSA, - 4096); - { - struct TALER_TokenIssuePublicKey token_pub = { - .public_key = pub, - }; - struct TALER_TokenIssuePrivateKey token_priv = { - .private_key = priv, - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Storing new key for slug %s of %s\n", - slug, - oc->hc->instance->settings.id); - iqs = TMH_db->insert_token_family_key (TMH_db->cls, - oc->hc->instance->settings.id, - slug, - &token_pub, - &token_priv, - min_valid_after, - valid_before); - GNUNET_CRYPTO_blind_sign_priv_decref (priv); - if (iqs <= 0) - { - enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - unsigned int http_status = 0; - - switch (iqs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - ec = TALER_EC_GENERIC_DB_STORE_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_break (0); - http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - ec = TALER_EC_GENERIC_DB_STORE_FAILED; - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* case listed to make compilers happy */ - GNUNET_assert (0); - } - reply_with_error (oc, - http_status, - ec, - "token_family_slug"); - return GNUNET_SYSERR; - } - - key.pub = token_pub; - key.valid_after = min_valid_after; - key.valid_before = valid_before; - } + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unsupported cipher family %s found in database for token family %s!\n", + key_details.token_family.cipher_spec, + slug); + GNUNET_free (key_details.token_family.cipher_spec); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "invalid cipher stored in local database for token family"); + return GNUNET_SYSERR; } - else + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Storing new key for slug %s of %s\n", + slug, + oc->hc->instance->settings.id); + iqs = TMH_db->insert_token_family_key (TMH_db->cls, + oc->hc->instance->settings.id, + slug, + &key.pub, + &token_priv, + key_expires, + key.valid_after, + key.valid_before); + GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key); + switch (iqs) { - key.pub = key_details.pub; - key.valid_after = key_details.valid_after; - key.valid_before = key_details.valid_before; + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + NULL); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); + return GNUNET_SYSERR; // FIXME: or retry? + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + reply_with_error (oc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + NULL); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; } - + *key_index = family->keys_len; GNUNET_array_append (family->keys, family->keys_len, key); - - *valid_after = key.valid_after; } - return GNUNET_OK; } @@ -1651,15 +1865,8 @@ serialize_order (struct OrderContext *oc) /* TODO: Remove h_pub. */ GNUNET_JSON_pack_data_auto ("h_pub", &key.pub.public_key->pub_key_hash), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_rsa_public_key ("rsa_pub", - key.pub.public_key->details. - rsa_public_key)), - // GNUNET_JSON_pack_allow_null( - // GNUNET_JSON_pack_data_auto ("cs_pub", - // &key.pub.public_key->details.cs_public_key)), - GNUNET_JSON_pack_int64 ("cipher", - key.pub.public_key->cipher), + TALER_JSON_pack_token_pub ("public_key", + &key.pub), GNUNET_JSON_pack_timestamp ("valid_after", key.valid_after), GNUNET_JSON_pack_timestamp ("valid_before", @@ -1715,9 +1922,7 @@ serialize_order (struct OrderContext *oc) GNUNET_JSON_pack_string ("token_family_slug", input->details.token.token_family_slug), GNUNET_JSON_pack_int64 ("number", - input->details.token.count), - GNUNET_JSON_pack_timestamp ("valid_after", - input->details.token.valid_after) + input->details.token.count) ); GNUNET_assert (0 == @@ -1741,8 +1946,8 @@ serialize_order (struct OrderContext *oc) output->details.token.token_family_slug), GNUNET_JSON_pack_int64 ("number", output->details.token.count), - GNUNET_JSON_pack_timestamp ("valid_after", - output->details.token.valid_after) + GNUNET_JSON_pack_int64 ("key_index", + output->details.token.key_index) ); GNUNET_assert (0 == @@ -1772,7 +1977,7 @@ serialize_order (struct OrderContext *oc) oc->parse_order.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("summary_i18n", - oc->parse_order.summary_i18n)), + (json_t *) oc->parse_order.summary_i18n)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("public_reorder_url", oc->parse_order.public_reorder_url)), @@ -1781,7 +1986,8 @@ serialize_order (struct OrderContext *oc) oc->parse_order.fulfillment_message)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n", - oc->parse_order.fulfillment_message_i18n)) + (json_t *) oc->parse_order. + fulfillment_message_i18n)) , GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", @@ -1808,7 +2014,8 @@ serialize_order (struct OrderContext *oc) oc->parse_order.delivery_date)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("delivery_location", - oc->parse_order.delivery_location)), + (json_t *) oc->parse_order. + delivery_location)), GNUNET_JSON_pack_string ("merchant_base_url", oc->parse_order.merchant_base_url), GNUNET_JSON_pack_object_steal ("merchant", @@ -2348,8 +2555,7 @@ parse_order (struct OrderContext *oc) * mostly because in GNUnet relative times can't * be negative. */ bool no_fee; - /* TODO: Move "amount" field to choices. This entails moving the - stefan-base fee calculation to the parse_choices phase. */ + const char *oid; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint64 ("version", @@ -2364,20 +2570,20 @@ parse_order (struct OrderContext *oc) &oc->parse_order.products), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("summary_i18n", - &oc->parse_order.summary_i18n), + GNUNET_JSON_spec_object_const ("summary_i18n", + &oc->parse_order.summary_i18n), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("order_id", - &oc->parse_order.order_id), + &oid), 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), + GNUNET_JSON_spec_object_const ("fulfillment_message_i18n", + &oc->parse_order.fulfillment_message_i18n), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("fulfillment_url", @@ -2420,8 +2626,8 @@ parse_order (struct OrderContext *oc) &oc->parse_order.max_fee), &no_fee), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_json ("delivery_location", - &oc->parse_order.delivery_location), + GNUNET_JSON_spec_object_const ("delivery_location", + &oc->parse_order.delivery_location), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("delivery_date", @@ -2521,7 +2727,11 @@ parse_order (struct OrderContext *oc) } /* Add order_id if it doesn't exist. */ - if (NULL == oc->parse_order.order_id) + if (NULL != oid) + { + oc->parse_order.order_id = GNUNET_strdup (oid); + } + else { char buf[256]; time_t timer; @@ -2649,10 +2859,14 @@ parse_order (struct OrderContext *oc) } } - if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time)) + if ( (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time)) || + (GNUNET_TIME_absolute_is_never (oc->parse_order.pay_deadline.abs_time)) ) { oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp ( settings->default_pay_delay); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Pay deadline was zero (or never), setting to %s\n", + GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline)); } else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time)) { @@ -2664,7 +2878,9 @@ parse_order (struct OrderContext *oc) NULL); return; } - + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Pay deadline is %s\n", + GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline)); if ( (! GNUNET_TIME_absolute_is_zero ( oc->parse_order.refund_deadline.abs_time)) && (GNUNET_TIME_absolute_is_past ( @@ -2824,7 +3040,8 @@ parse_choices (struct OrderContext *oc) }; enum GNUNET_GenericReturnValue ret; - ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i), + ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, + i), spec, &error_name, &error_line); @@ -2841,7 +3058,8 @@ parse_choices (struct OrderContext *oc) return; } - if (0 == json_array_size (jinputs) && 0 == json_array_size (joutputs)) + if ( (0 == json_array_size (jinputs)) && + (0 == json_array_size (joutputs)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Choice #%u must have at least one input or output\n", @@ -2859,20 +3077,17 @@ parse_choices (struct OrderContext *oc) size_t idx; json_array_foreach ((json_t *) jinputs, idx, jinput) { - struct TALER_MerchantContractInput input = {.details.token.count = 1}; + struct TALER_MerchantContractInput input = { + .details.token.count = 1 + }; const char *kind; const char *ierror_name; unsigned int ierror_line; - struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &input.details.token.token_family_slug), - /* TODO: Remove valid_after field for inputs, - use current system time instead. */ - GNUNET_JSON_spec_timestamp ("valid_after", - &input.details.token.valid_after), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &input.details.token.count), @@ -2917,10 +3132,9 @@ parse_choices (struct OrderContext *oc) continue; } - if (GNUNET_OK != set_token_family (oc, - input.details.token.token_family_slug - , - &input.details.token.valid_after)) + if (GNUNET_OK != + add_input_token_family (oc, + input.details.token.token_family_slug)) { /* error is already scheduled, return. */ return; @@ -2937,24 +3151,27 @@ parse_choices (struct OrderContext *oc) size_t idx; json_array_foreach ((json_t *) joutputs, idx, joutput) { - struct TALER_MerchantContractOutput output = { .details.token.count = 1} - ; + struct TALER_MerchantContractOutput output = { + .details.token.count = 1 + }; const char *kind; const char *ierror_name; unsigned int ierror_line; - + bool nots; + struct GNUNET_TIME_Timestamp valid_at; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_string ("token_family_slug", &output.details.token.token_family_slug), - /* TODO: Make valid_after optional, default to current system time. */ - GNUNET_JSON_spec_timestamp ("valid_after", - &output.details.token.valid_after), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &output.details.token.count), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("valid_at", + &valid_at), + &nots), GNUNET_JSON_spec_end () }; @@ -2976,9 +3193,35 @@ parse_choices (struct OrderContext *oc) ierror_name); return; } + if (nots) + { + valid_at = oc->parse_order.pay_deadline; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Looking for output token valid at pay deadline %s\n", + GNUNET_TIME_timestamp2s (valid_at)); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Looking for output token valid at %s\n", + GNUNET_TIME_timestamp2s (valid_at)); + } - output.type = TMH_contract_output_type_from_string (kind); + if (GNUNET_TIME_timestamp_cmp (valid_at, + <, + oc->parse_order.pay_deadline)) + { + GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (ispec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "valid_at before pay_deadline"); + return; + } + + output.type = TMH_contract_output_type_from_string (kind); if (TALER_MCOT_INVALID == output.type) { GNUNET_JSON_parse_free (spec); @@ -2999,10 +3242,11 @@ parse_choices (struct OrderContext *oc) continue; } - if (GNUNET_OK != set_token_family (oc, - output.details.token. - token_family_slug, - &output.details.token.valid_after)) + if (GNUNET_OK != + add_output_token_family (oc, + output.details.token.token_family_slug, + valid_at, + &output.details.token.key_index)) { /* Error is already scheduled, return. */ return; diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c b/src/backend/taler-merchant-httpd_private-post-token-families.c @@ -54,6 +54,9 @@ token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1, tf2->description)) && (1 == json_equal (tf1->description_i18n, tf2->description_i18n)) && + ( (tf1->extra_data == tf2->extra_data) || + (1 == json_equal (tf1->extra_data, + tf2->extra_data)) ) && (GNUNET_TIME_timestamp_cmp (tf1->valid_after, ==, tf2->valid_after)) && @@ -63,6 +66,12 @@ token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1, (GNUNET_TIME_relative_cmp (tf1->duration, ==, tf2->duration)) && + (GNUNET_TIME_relative_cmp (tf1->validity_granularity, + ==, + tf2->validity_granularity)) && + (GNUNET_TIME_relative_cmp (tf1->start_offset, + ==, + tf2->start_offset)) && (tf1->kind == tf2->kind) ); } @@ -89,6 +98,10 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, &details.description_i18n), NULL), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("extra_data", + &details.extra_data), + NULL), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_timestamp ("valid_after", &details.valid_after), &no_valid_after), @@ -96,8 +109,12 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, &details.valid_before), GNUNET_JSON_spec_relative_time ("duration", &details.duration), - GNUNET_JSON_spec_relative_time ("rounding", - &details.rounding), + GNUNET_JSON_spec_relative_time ("validity_granularity", + &details.validity_granularity), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_relative_time ("start_offset", + &details.start_offset), + NULL), GNUNET_JSON_spec_string ("kind", &kind), GNUNET_JSON_spec_end () @@ -133,20 +150,23 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "valid_before"); + "valid_after >= valid_before"); } - /* Ensure that valid_after is not in the past */ - if (GNUNET_TIME_timestamp_cmp (details.valid_after, - <, - now)) + /* Ensure that duration exceeds rounding plus start_offset */ + if (GNUNET_TIME_relative_cmp (details.duration, + <, + GNUNET_TIME_relative_add (details. + validity_granularity, + details.start_offset)) + ) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "valid_after"); + "duration below validity_granularity plus start_offset"); } if (0 == @@ -182,31 +202,40 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, !=, - details.rounding) && + details.validity_granularity) && + GNUNET_TIME_relative_cmp (GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_DAYS, + 90), + !=, + details.validity_granularity) && GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, !=, - details.rounding) && + details.validity_granularity) && + GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_WEEKS, + !=, + details.validity_granularity) && GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, !=, - details.rounding) && + details.validity_granularity) && GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, !=, - details.rounding) && + details.validity_granularity) && GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, !=, - details.rounding) + details.validity_granularity) ) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Received invalid rounding value: %s\n", - GNUNET_STRINGS_relative_time_to_string (details.rounding, - true)); + "Received invalid validity_granularity value: %s\n", + GNUNET_STRINGS_relative_time_to_string (details. + validity_granularity, + false)); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "rounding"); + "validity_granularity"); } /* finally, interact with DB until no serialization error */ @@ -215,6 +244,7 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, /* Test if a token family of this id is known */ struct TALER_MERCHANTDB_TokenFamilyDetails existing; + TMH_db->preflight (TMH_db->cls); if (GNUNET_OK != TMH_db->start (TMH_db->cls, "/post tokenfamilies")) @@ -226,69 +256,109 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh, TALER_EC_GENERIC_DB_START_FAILED, NULL); } - qs = TMH_db->lookup_token_family (TMH_db->cls, + qs = TMH_db->insert_token_family (TMH_db->cls, mi->settings.id, details.slug, - &existing); + &details); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "insert_token_family returned %d\n", + (int) qs); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: - /* Clean up and fail hard */ GNUNET_break (0); TMH_db->rollback (TMH_db->cls); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_token_family"); case GNUNET_DB_STATUS_SOFT_ERROR: - /* restart transaction */ - goto retry; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* Good, we can proceed! */ + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* idempotency check: is existing == details? */ + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + qs = TMH_db->lookup_token_family (TMH_db->cls, + mi->settings.id, + details.slug, + &existing); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "lookup_token_family returned %d\n", + (int) qs); + switch (qs) { - bool eq; - - eq = token_families_equal (&details, - &existing); - TALER_MERCHANTDB_token_family_details_free (&existing); + case GNUNET_DB_STATUS_HARD_ERROR: + /* Clean up and fail hard */ + GNUNET_break (0); TMH_db->rollback (TMH_db->cls); GNUNET_JSON_parse_free (spec); - return eq - ? TALER_MHD_reply_static (connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0) - : TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT, - details.slug); - } - } /* end switch (qs) */ + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_token_family"); + case GNUNET_DB_STATUS_SOFT_ERROR: + TMH_db->rollback (TMH_db->cls); + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + "lookup_token_family failed after insert_token_family failed"); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + { + bool eq; - qs = TMH_db->insert_token_family (TMH_db->cls, - mi->settings.id, - details.slug, - &details); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - TMH_db->rollback (TMH_db->cls); + eq = token_families_equal (&details, + &existing); + TALER_MERCHANTDB_token_family_details_free (&existing); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return eq + ? TALER_MHD_reply_static ( + connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0) + : TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT, + details.slug); + } + } break; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: qs = TMH_db->commit (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + /* Clean up and fail hard */ + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "insert_token_family"); + case GNUNET_DB_STATUS_SOFT_ERROR: + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; + } + break; } -retry: - GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs); - TMH_db->rollback (TMH_db->cls); - } /* for RETRIES loop */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + TMH_db->rollback (TMH_db->cls); + else + break; + } /* for(i... MAX_RETRIES) */ + GNUNET_JSON_parse_free (spec); if (qs < 0) { @@ -296,9 +366,7 @@ retry: return TALER_MHD_reply_with_error ( connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - (GNUNET_DB_STATUS_SOFT_ERROR == qs) - ? TALER_EC_GENERIC_DB_SOFT_FAILURE - : TALER_EC_GENERIC_DB_COMMIT_FAILED, + TALER_EC_GENERIC_DB_SOFT_FAILURE, NULL); } return TALER_MHD_reply_static (connection, diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -128,6 +128,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_delete_template.h pg_delete_template.c \ pg_insert_template.h pg_insert_template.c \ pg_update_template.h pg_update_template.c \ + pg_lookup_token_family_keys.h pg_lookup_token_family_keys.c \ pg_lookup_templates.h pg_lookup_templates.c \ pg_lookup_template.h pg_lookup_template.c \ pg_lookup_all_products.h pg_lookup_all_products.c \ diff --git a/src/backenddb/merchant-0008.sql b/src/backenddb/merchant-0008.sql @@ -44,11 +44,15 @@ COMMENT ON COLUMN merchant_issued_tokens.blind_sig ALTER TABLE merchant_spent_tokens RENAME TO merchant_used_tokens; -ALTER TABLE merchant_token_families ADD COLUMN rounding BIGINT NOT NULL; +ALTER TABLE merchant_token_families + ADD COLUMN rounding BIGINT NOT NULL; + +ALTER TABLE merchant_token_families + RENAME COLUMN redeemed TO used; + COMMENT ON COLUMN merchant_token_families.rounding IS 'Token start date rounding granularity.'; -ALTER TABLE merchant_token_families RENAME COLUMN redeemed TO used; -- Complete transaction COMMIT; diff --git a/src/backenddb/merchant-0013.sql b/src/backenddb/merchant-0013.sql @@ -28,11 +28,45 @@ SELECT _v.register_patch('merchant-0013', NULL, NULL); SET search_path TO merchant; -- Slug was incorrectly set to be globally unique, is only --- unique per instance! +-- unique per instance! Drop the constraint and add correct one. ALTER TABLE merchant_token_families DROP CONSTRAINT merchant_token_families_slug_key, - ADD UNIQUE (merchant_serial,slug); - + ADD UNIQUE (merchant_serial,slug), + DROP COLUMN rounding, -- replaced by validity_granularity + ADD validity_granularity INT8 NOT NULL CHECK + (validity_granularity IN (60000000,3600000000,86400000000,604800000000,2592000000000,7776000000000,31536000000000)) + DEFAULT (2592000000000), -- 30 days + ADD start_offset INT8 NOT NULL DEFAULT (0), + ADD cipher_choice TEXT NOT NULL DEFAULT ('rsa(2048)'), + ADD extra_data TEXT DEFAULT NULL; + +COMMENT ON COLUMN merchant_token_families.validity_granularity + IS 'To compute key lifetimes, we first round the payment deadline down to a multiple of this time; supported values are one minute, one hour, a day, a week, 30 days, 90 days or a year (indicatited using 365 days); adding the start_offset gets the start validity time; adding the duration to get the signature_valid_until value for the key'; +COMMENT ON COLUMN merchant_token_families.start_offset + IS 'This allows shifting the validity period of signatures to start a bit before the time rounded to the precision. For example, Swiss vignettes are valid for 14 months, from December of year X to January of year X+2. This can be achieve by setting a start_offset of 30 days, and a duration of 14 months and a precision of 1 year. The value given is in microseconds (but will be rounded to seconds).'; +COMMENT ON COLUMN merchant_token_families.cipher_choice + IS 'Specifies the type of cipher that should be used for this token family. Currently supported values are "cs" and "rsa($LEN)" where $LEN is the key length in bits.'; +COMMENT ON COLUMN merchant_token_families.extra_data + IS 'JSON field with family-specific meta data, such as the trusted_domains for subscriptions or expected_domains for discount tokens'; + + +-- Keep proper periods for token keys. +ALTER TABLE merchant_token_family_keys + DROP valid_before, + DROP valid_after, + ADD signature_validity_start INT8 NOT NULL DEFAULT (0), + ADD signature_validity_end INT8 NOT NULL DEFAULT (0), + ADD private_key_deleted_at INT8 NOT NULL DEFAULT (0), + ADD private_key_created_at INT8 NOT NULL DEFAULT (0); + +COMMENT ON COLUMN merchant_token_family_keys.signature_validity_start + IS 'Specifies the earliest time at which tokens signed with this key can be considered valid. Allows tokens to be issued way in advance of their validity.'; +COMMENT ON COLUMN merchant_token_family_keys.signature_validity_end + IS 'Specifies when the tokens signed by this key expire.'; +COMMENT ON COLUMN merchant_token_family_keys.private_key_deleted_at + IS 'Specifies how long tokens signed by this key can be created, that is the point at which the private key may be deleted. Computed by determining when the *next* validity period starts, or when the overall token family validity period ends.'; +COMMENT ON COLUMN merchant_token_family_keys.private_key_created_at + IS 'Specifies when the private key was created. Not terribly useful, mostly for debugging.'; -- Function to replace placeholders in a string with a given value CREATE OR REPLACE FUNCTION replace_placeholder( diff --git a/src/backenddb/pg_insert_token_family.c b/src/backenddb/pg_insert_token_family.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,12 +25,13 @@ #include "pg_insert_token_family.h" #include "pg_helper.h" + enum GNUNET_DB_QueryStatus -TMH_PG_insert_token_family (void *cls, - const char *instance_id, - const char *token_family_slug, - const struct TALER_MERCHANTDB_TokenFamilyDetails * - details) +TMH_PG_insert_token_family ( + void *cls, + const char *instance_id, + const char *token_family_slug, + const struct TALER_MERCHANTDB_TokenFamilyDetails *details) { struct PostgresClosure *pg = cls; const char *kind; @@ -47,7 +48,6 @@ TMH_PG_insert_token_family (void *cls, GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } - check_connection (pg); PREPARE (pg, "insert_token_family", @@ -57,14 +57,17 @@ TMH_PG_insert_token_family (void *cls, ",name" ",description" ",description_i18n" + ",extra_data" ",valid_after" ",valid_before" ",duration" - ",rounding" + ",validity_granularity" + ",start_offset" ",kind)" - " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9, $10" + " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" " FROM merchant_instances" - " WHERE merchant_id=$1"); + " WHERE merchant_id=$1" + " ON CONFLICT DO NOTHING;"); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), @@ -72,10 +75,14 @@ TMH_PG_insert_token_family (void *cls, GNUNET_PQ_query_param_string (details->name), GNUNET_PQ_query_param_string (details->description), TALER_PQ_query_param_json (details->description_i18n), + NULL == details->extra_data + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (details->extra_data), GNUNET_PQ_query_param_timestamp (&details->valid_after), GNUNET_PQ_query_param_timestamp (&details->valid_before), GNUNET_PQ_query_param_relative_time (&details->duration), - GNUNET_PQ_query_param_relative_time (&details->rounding), + GNUNET_PQ_query_param_relative_time (&details->validity_granularity), + GNUNET_PQ_query_param_relative_time (&details->start_offset), GNUNET_PQ_query_param_string (kind), GNUNET_PQ_query_param_end }; diff --git a/src/backenddb/pg_insert_token_family_key.c b/src/backenddb/pg_insert_token_family_key.c @@ -35,10 +35,13 @@ TMH_PG_insert_token_family_key ( const char *token_family_slug, const struct TALER_TokenIssuePublicKey *pub, const struct TALER_TokenIssuePrivateKey *priv, - const struct GNUNET_TIME_Timestamp valid_after, - const struct GNUNET_TIME_Timestamp valid_before) + struct GNUNET_TIME_Timestamp key_expires, + struct GNUNET_TIME_Timestamp valid_after, + struct GNUNET_TIME_Timestamp valid_before) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); const char *cipher = NULL; struct GNUNET_HashCode pub_hash; @@ -46,14 +49,16 @@ TMH_PG_insert_token_family_key ( { case GNUNET_CRYPTO_BSA_RSA: cipher = "rsa"; - GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key->details.rsa_public_key, - &pub_hash); + GNUNET_CRYPTO_rsa_public_key_hash ( + pub->public_key->details.rsa_public_key, + &pub_hash); break; case GNUNET_CRYPTO_BSA_CS: cipher = "cs"; - GNUNET_CRYPTO_hash (&pub->public_key->details.cs_public_key, - sizeof (pub->public_key->details.cs_public_key), - &pub_hash); + GNUNET_CRYPTO_hash ( + &pub->public_key->details.cs_public_key, + sizeof (pub->public_key->details.cs_public_key), + &pub_hash); break; case GNUNET_CRYPTO_BSA_INVALID: GNUNET_break (0); @@ -68,7 +73,6 @@ TMH_PG_insert_token_family_key ( valid_after.abs_time)); GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( valid_before.abs_time)); - PREPARE (pg, "token_family_key_insert", "INSERT INTO merchant_token_family_keys " @@ -76,31 +80,43 @@ TMH_PG_insert_token_family_key ( ",pub" ",h_pub" ",priv" - ",valid_after" - ",valid_before" + ",private_key_created_at" + ",private_key_deleted_at" + ",signature_validity_start" + ",signature_validity_end" ",cipher)" - " SELECT token_family_serial, $2, $3, $4, $5, $6, $7" + " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9" " FROM merchant_token_families" " WHERE (slug = $1)" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" - " WHERE merchant_id=$8)"); + " WHERE merchant_id=$10)"); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (token_family_slug), GNUNET_PQ_query_param_blind_sign_pub (pub->public_key), GNUNET_PQ_query_param_auto_from_type (&pub->public_key->pub_key_hash), GNUNET_PQ_query_param_blind_sign_priv (priv->private_key), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_timestamp (&key_expires), GNUNET_PQ_query_param_timestamp (&valid_after), GNUNET_PQ_query_param_timestamp (&valid_before), GNUNET_PQ_query_param_string (cipher), GNUNET_PQ_query_param_string (merchant_id), GNUNET_PQ_query_param_end }; + enum GNUNET_DB_QueryStatus qs; - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "token_family_key_insert", - params); + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "token_family_key_insert", + params); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Insert into MTFK %s with valid [%llu,%llu] got %d\n", + token_family_slug, + (unsigned long long) valid_after.abs_time.abs_value_us, + (unsigned long long) valid_before.abs_time.abs_value_us, + (int) qs); + return qs; } } diff --git a/src/backenddb/pg_insert_token_family_key.h b/src/backenddb/pg_insert_token_family_key.h @@ -34,8 +34,10 @@ * @param token_family_slug slug of the token family to insert the key for * @param pub public key to insert * @param priv private key to insert - * @param valid_after start of validity period for this key - * @param valid_before end of validity period for this key + * @param key_expires when does the private key expire (because + * the validity period of the next token family key starts) + * @param valid_after start of validity period for signatures with this key + * @param valid_before end of validity period for signatures with this key * @return database result code */ enum GNUNET_DB_QueryStatus @@ -45,7 +47,8 @@ TMH_PG_insert_token_family_key ( const char *token_family_slug, const struct TALER_TokenIssuePublicKey *pub, const struct TALER_TokenIssuePrivateKey *priv, - const struct GNUNET_TIME_Timestamp valid_after, - const struct GNUNET_TIME_Timestamp valid_before); + struct GNUNET_TIME_Timestamp key_expires, + struct GNUNET_TIME_Timestamp valid_after, + struct GNUNET_TIME_Timestamp valid_before); #endif diff --git a/src/backenddb/pg_lookup_token_family.c b/src/backenddb/pg_lookup_token_family.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,11 +25,13 @@ #include "pg_lookup_token_family.h" #include "pg_helper.h" + enum GNUNET_DB_QueryStatus -TMH_PG_lookup_token_family (void *cls, - const char *instance_id, - const char *token_family_slug, - struct TALER_MERCHANTDB_TokenFamilyDetails *details) +TMH_PG_lookup_token_family ( + void *cls, + const char *instance_id, + const char *token_family_slug, + struct TALER_MERCHANTDB_TokenFamilyDetails *details) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -62,12 +64,20 @@ TMH_PG_lookup_token_family (void *cls, &details->description), TALER_PQ_result_spec_json ("description_i18n", &details->description_i18n), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("extra_data", + &details->extra_data), + NULL), GNUNET_PQ_result_spec_timestamp ("valid_after", &details->valid_after), GNUNET_PQ_result_spec_timestamp ("valid_before", &details->valid_before), GNUNET_PQ_result_spec_relative_time ("duration", &details->duration), + GNUNET_PQ_result_spec_relative_time ("validity_granularity", + &details->validity_granularity), + GNUNET_PQ_result_spec_relative_time ("start_offset", + &details->start_offset), GNUNET_PQ_result_spec_string ("kind", &kind), GNUNET_PQ_result_spec_uint64 ("issued", @@ -86,9 +96,12 @@ TMH_PG_lookup_token_family (void *cls, ",name" ",description" ",description_i18n" + ",extra_data" ",valid_after" ",valid_before" ",duration" + ",validity_granularity" + ",start_offset" ",kind" ",issued" ",used" @@ -97,6 +110,9 @@ TMH_PG_lookup_token_family (void *cls, " USING (merchant_serial)" " WHERE merchant_instances.merchant_id=$1" " AND merchant_token_families.slug=$2"); + memset (details, + 0, + sizeof (*details)); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_token_family", params, diff --git a/src/backenddb/pg_lookup_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c @@ -34,16 +34,16 @@ TMH_PG_lookup_token_family_key ( void *cls, const char *instance_id, const char *token_family_slug, - struct GNUNET_TIME_Timestamp min_valid_after, - struct GNUNET_TIME_Timestamp max_valid_after, + struct GNUNET_TIME_Timestamp valid_at, + struct GNUNET_TIME_Timestamp sign_until, struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (token_family_slug), - GNUNET_PQ_query_param_timestamp (&min_valid_after), - GNUNET_PQ_query_param_timestamp (&max_valid_after), + GNUNET_PQ_query_param_timestamp (&valid_at), + GNUNET_PQ_query_param_timestamp (&sign_until), GNUNET_PQ_query_param_end }; @@ -54,29 +54,33 @@ TMH_PG_lookup_token_family_key ( " h_pub" ",pub" ",priv" - ",cipher" - ",merchant_token_family_keys.valid_after AS key_valid_after" - ",merchant_token_family_keys.valid_before AS key_valid_before" + ",cipher_choice" + ",mtfk.signature_validity_start" + ",mtfk.signature_validity_end" + ",mtfk.private_key_deleted_at" ",slug" ",name" ",description" ",description_i18n" - ",merchant_token_families.valid_after" - ",merchant_token_families.valid_before" + ",mtf.valid_after" + ",mtf.valid_before" ",duration" + ",validity_granularity" + ",start_offset" ",kind" ",issued" ",used" - " FROM merchant_token_families" - " LEFT JOIN merchant_token_family_keys" - " ON merchant_token_families.token_family_serial = merchant_token_family_keys.token_family_serial" - " AND merchant_token_family_keys.valid_after >= $3" - " AND merchant_token_family_keys.valid_after <= $4" /* TODO: Should this be < instead of <=? */ - " JOIN merchant_instances" + " FROM merchant_token_families mtf" + " LEFT JOIN merchant_token_family_keys mtfk" + " USING (token_family_serial)" + " JOIN merchant_instances mi" " USING (merchant_serial)" - " WHERE merchant_instances.merchant_id=$1" + " WHERE mi.merchant_id=$1" " AND slug=$2" - " ORDER BY merchant_token_family_keys.valid_after ASC" + " AND COALESCE ($3 >= mtfk.signature_validity_start, TRUE)" + " AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)" + " AND COALESCE ($4 <= mtfk.private_key_deleted_at, TRUE)" + " ORDER BY mtfk.signature_validity_start ASC" " LIMIT 1"); if (NULL == details) @@ -85,7 +89,6 @@ TMH_PG_lookup_token_family_key ( GNUNET_PQ_result_spec_end }; - check_connection (pg); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_token_family_key", params, @@ -105,17 +108,23 @@ TMH_PG_lookup_token_family_key ( &details->priv.private_key), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_timestamp ("key_valid_after", - &details->valid_after), + GNUNET_PQ_result_spec_timestamp ("signature_validity_start", + &details->signature_validity_start), NULL), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_timestamp ("key_valid_before", - &details->valid_before), + GNUNET_PQ_result_spec_timestamp ("signature_validity_end", + &details->signature_validity_end), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at", + &details->private_key_deleted_at), NULL), GNUNET_PQ_result_spec_string ("slug", &details->token_family.slug), GNUNET_PQ_result_spec_string ("name", &details->token_family.name), + GNUNET_PQ_result_spec_string ("cipher_choice", + &details->token_family.cipher_spec), GNUNET_PQ_result_spec_string ("description", &details->token_family.description), TALER_PQ_result_spec_json ("description_i18n", @@ -126,6 +135,11 @@ TMH_PG_lookup_token_family_key ( &details->token_family.valid_before), GNUNET_PQ_result_spec_relative_time ("duration", &details->token_family.duration), + GNUNET_PQ_result_spec_relative_time ("validity_granularity", + &details->token_family. + validity_granularity), + GNUNET_PQ_result_spec_relative_time ("start_offset", + &details->token_family.start_offset), GNUNET_PQ_result_spec_string ("kind", &kind), GNUNET_PQ_result_spec_uint64 ("issued", diff --git a/src/backenddb/pg_lookup_token_family_key.h b/src/backenddb/pg_lookup_token_family_key.h @@ -33,19 +33,20 @@ * @param cls closure * @param instance_id instance to lookup token family key for * @param token_family_slug slug of token family to lookup - * @param min_valid_after lower bound of the start of the key validation period - * @param max_valid_after strict upper bound of the start of the key validation period + * @param valid_at find a key with a validity period that includes this time + * @param sign_until find a private key that can sign until this time * @param[out] details set to the token family key details on success, can be NULL * (in that case we only want to check if the token family key exists) * @return database result code */ enum GNUNET_DB_QueryStatus -TMH_PG_lookup_token_family_key (void *cls, - const char *instance_id, - const char *token_family_slug, - struct GNUNET_TIME_Timestamp min_valid_after, - struct GNUNET_TIME_Timestamp max_valid_after, - struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details); +TMH_PG_lookup_token_family_key ( + void *cls, + const char *instance_id, + const char *token_family_slug, + struct GNUNET_TIME_Timestamp valid_at, + struct GNUNET_TIME_Timestamp sign_until, + struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details); #endif diff --git a/src/backenddb/pg_lookup_token_family_keys.c b/src/backenddb/pg_lookup_token_family_keys.c @@ -0,0 +1,225 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_lookup_token_family_keys.c + * @brief Implementation of the lookup_token_family_keys function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_token_family_keys.h" +#include "pg_helper.h" + + +/** + * Closure for lookup_token_keys_cb(). + */ +struct Context +{ + /** + * Postgres handle. + */ + struct PostgresClosure *pg; + + /** + * Function to call on each result. + */ + TALER_MERCHANTDB_TokenKeyCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Did database result extraction fail? + */ + bool extract_failed; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results about products. + * + * @param[in,out] cls of type `struct LookupProductsContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lookup_token_keys_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct Context *ctx = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + struct TALER_MERCHANTDB_TokenFamilyKeyDetails details; + char *kind; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_blind_sign_pub ("pub", + &details.pub.public_key), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_blind_sign_priv ("priv", + &details.priv.private_key), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("signature_validity_start", + &details.signature_validity_start), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("signature_validity_end", + &details.signature_validity_end), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at", + &details.private_key_deleted_at), + NULL), + GNUNET_PQ_result_spec_string ("slug", + &details.token_family.slug), + GNUNET_PQ_result_spec_string ("name", + &details.token_family.name), + GNUNET_PQ_result_spec_string ("description", + &details.token_family.description), + GNUNET_PQ_result_spec_string ("cipher_choice", + &details.token_family.cipher_spec), + TALER_PQ_result_spec_json ("description_i18n", + &details.token_family.description_i18n), + GNUNET_PQ_result_spec_timestamp ("valid_after", + &details.token_family.valid_after), + GNUNET_PQ_result_spec_timestamp ("valid_before", + &details.token_family.valid_before), + GNUNET_PQ_result_spec_relative_time ("duration", + &details.token_family.duration), + GNUNET_PQ_result_spec_relative_time ("validity_granularity", + &details.token_family. + validity_granularity), + GNUNET_PQ_result_spec_relative_time ("start_offset", + &details.token_family.start_offset), + GNUNET_PQ_result_spec_string ("kind", + &kind), + GNUNET_PQ_result_spec_uint64 ("issued", + &details.token_family.issued), + GNUNET_PQ_result_spec_uint64 ("used", + &details.token_family.used), + GNUNET_PQ_result_spec_end + }; + + memset (&details, + 0, + sizeof (details)); + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->extract_failed = true; + return; + } + if (0 == strcmp (kind, + "discount")) + details.token_family.kind = TALER_MERCHANTDB_TFK_Discount; + else if (0 == strcmp (kind, + "subscription")) + details.token_family.kind = TALER_MERCHANTDB_TFK_Subscription; + else + { + GNUNET_break (0); + GNUNET_free (kind); + GNUNET_PQ_cleanup_result (rs); + return; + } + ctx->cb (ctx->cb_cls, + &details); + if (NULL != details.pub.public_key) /* guard against GNUnet 0.23 bug! */ + GNUNET_PQ_cleanup_result (rs); + } +} + + +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_token_family_keys ( + void *cls, + const char *instance_id, + const char *token_family_slug, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + TALER_MERCHANTDB_TokenKeyCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (token_family_slug), + GNUNET_PQ_query_param_timestamp (&start_time), + GNUNET_PQ_query_param_timestamp (&end_time), + GNUNET_PQ_query_param_end + }; + struct Context ctx = { + .pg = pg, + .cb = cb, + .cb_cls = cb_cls + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + PREPARE (pg, + "lookup_token_family_keys", + "SELECT" + " h_pub" + ",mtfk.pub" + ",mtfk.priv" + ",cipher_choice" + ",mtfk.signature_validity_start" + ",mtfk.signature_validity_end" + ",mtfk.private_key_deleted_at" + ",slug" + ",name" + ",description" + ",description_i18n" + ",mtf.valid_after" + ",mtf.valid_before" + ",duration" + ",validity_granularity" + ",start_offset" + ",kind" + ",issued" + ",used" + " FROM merchant_token_families mtf" + " LEFT JOIN merchant_token_family_keys mtfk" + " USING (token_family_serial)" + " JOIN merchant_instances mi" + " USING (merchant_serial)" + " WHERE mi.merchant_id=$1" + " AND slug=$2" + " AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)" + " AND COALESCE ($4 >= mtfk.signature_validity_start, TRUE);"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "lookup_token_family_keys", + params, + &lookup_token_keys_cb, + &ctx); + if (ctx.extract_failed) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/backenddb/pg_lookup_token_family_keys.h b/src/backenddb/pg_lookup_token_family_keys.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_lookup_token_family_keys.h + * @brief implementation of the lookup_token_family_keys function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_LOOKUP_TOKEN_FAMILY_KEYS_H +#define PG_LOOKUP_TOKEN_FAMILY_KEYS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Lookup token family keys that may be used for a payment. + * + * @param cls closure + * @param instance_id instance to lookup token family key for + * @param token_family_slug slug of token family to lookup + * @param start_time signature validity start the keys must fall into + * @param end_time signature validity end the keys must fall into + * @param cb function to call with each matching key + * @param cb_cls closure for @a cb + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_lookup_token_family_keys ( + void *cls, + const char *instance_id, + const char *token_family_slug, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + TALER_MERCHANTDB_TokenKeyCallback cb, + void *cb_cls); + + +#endif diff --git a/src/backenddb/pg_update_token_family.c b/src/backenddb/pg_update_token_family.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2023, 2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,11 +25,13 @@ #include "pg_update_token_family.h" #include "pg_helper.h" + enum GNUNET_DB_QueryStatus -TMH_PG_update_token_family (void *cls, - const char *instance_id, - const char *token_family_slug, - const struct TALER_MERCHANTDB_TokenFamilyDetails *details) +TMH_PG_update_token_family ( + void *cls, + const char *instance_id, + const char *token_family_slug, + const struct TALER_MERCHANTDB_TokenFamilyDetails *details) { struct PostgresClosure *pg = cls; @@ -39,9 +41,9 @@ TMH_PG_update_token_family (void *cls, GNUNET_PQ_query_param_string (details->name), GNUNET_PQ_query_param_string (details->description), TALER_PQ_query_param_json (details->description_i18n), + TALER_PQ_query_param_json (details->extra_data), GNUNET_PQ_query_param_timestamp (&details->valid_after), GNUNET_PQ_query_param_timestamp (&details->valid_before), - GNUNET_PQ_query_param_relative_time (&details->duration), GNUNET_PQ_query_param_end }; @@ -52,9 +54,9 @@ TMH_PG_update_token_family (void *cls, " name=$3" ",description=$4" ",description_i18n=$5" - ",valid_after=$6" - ",valid_before=$7" - ",duration=$8" + ",extra_data=$6" + ",valid_after=$7" + ",valid_before=$8" " WHERE merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -77,6 +77,7 @@ #include "pg_delete_product.h" #include "pg_insert_product.h" #include "pg_update_product.h" +#include "pg_lookup_token_family_keys.h" #include "pg_lock_product.h" #include "pg_expire_locks.h" #include "pg_delete_order.h" @@ -559,6 +560,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_insert_deposit_to_transfer; plugin->insert_transfer = &TMH_PG_insert_transfer; + plugin->lookup_token_family_keys + = &TMH_PG_lookup_token_family_keys; plugin->insert_transfer_details = &TMH_PG_insert_transfer_details; plugin->store_wire_fee_by_exchange diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -1981,6 +1981,11 @@ struct TALER_MERCHANT_TokenFamilyGetResponse const json_t *description_i18n; /** + * Additional data about the token family (such as expected_domains). + */ + const json_t *extra_data; + + /** * Start time of the token family's validity period. */ struct GNUNET_TIME_Timestamp valid_after; @@ -1996,6 +2001,17 @@ struct TALER_MERCHANT_TokenFamilyGetResponse struct GNUNET_TIME_Relative duration; /** + * Granularity of the validity periods of the token. + */ + struct GNUNET_TIME_Relative validity_granularity; + + /** + * Offset subtracted from the rounded start time to determine + * the actual start time. + */ + struct GNUNET_TIME_Relative start_offset; + + /** * Kind of token family, "subscription" or "discount". */ const char *kind; @@ -2064,10 +2080,13 @@ typedef void * @param name human-readable name for the token family * @param description description of the token family * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions + * @param extra_data additional meta-data about the token family * @param valid_after when the token family becomes valid * @param valid_before when the token family expires * @param duration how long tokens issued by this token family are valid for - * @param rounding rounding duration of token family + * @param validity_granularity rounding duration of token family + * @param start_offset time to subtract after rounding down to get + * the actual start time for each round * @param kind kind of token family, "subscription" or "discount" * @param cb function to call with the backend's result * @param cb_cls closure for @a cb @@ -2081,14 +2100,17 @@ TALER_MERCHANT_token_families_post ( const char *name, const char *description, const json_t *description_i18n, + const json_t *extra_data, struct GNUNET_TIME_Timestamp valid_after, struct GNUNET_TIME_Timestamp valid_before, struct GNUNET_TIME_Relative duration, - struct GNUNET_TIME_Relative rounding, + struct GNUNET_TIME_Relative validity_granularity, + struct GNUNET_TIME_Relative start_offset, const char *kind, TALER_MERCHANT_TokenFamiliesPostCallback cb, void *cb_cls); + /** * Cancel POST /tokenfamilies operation. * @@ -2098,6 +2120,10 @@ void TALER_MERCHANT_token_families_post_cancel ( struct TALER_MERCHANT_TokenFamiliesPostHandle *handle); + +/* FIXME: token_families_patch API is missing... */ + + /* ********************* /orders ************************** */ diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -1215,6 +1215,20 @@ struct TALER_MERCHANTDB_TokenFamilyDetails json_t *description_i18n; /** + * Meta-data associated with the token family. + * Includes information like "trusted_domains" or + * "expected_domains", if set. + */ + json_t *extra_data; + + /** + * Cipher that should be used for this token family. Note: We do not expose + * this over the API and do not let clients set it. NULL for default (when + * calling database). + */ + char *cipher_spec; + + /** * Start time of the token family duration. */ struct GNUNET_TIME_Timestamp valid_after; @@ -1225,14 +1239,21 @@ struct TALER_MERCHANTDB_TokenFamilyDetails struct GNUNET_TIME_Timestamp valid_before; /** - * Validity duration of the token family. + * Validity duration of the token family. Must be larger or + * equal to @a rounding plus @a start_offset_s. */ struct GNUNET_TIME_Relative duration; /** * Rounding duration of the token family. */ - struct GNUNET_TIME_Relative rounding; + struct GNUNET_TIME_Relative validity_granularity; + + /** + * Offset (in seconds) to subtract from the rounded + * validity start period. + */ + struct GNUNET_TIME_Relative start_offset; /** * Token family kind. @@ -1258,13 +1279,20 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails { /** * Tokens signed with this key are valid from this date on. + * This is the time the key was created. */ - struct GNUNET_TIME_Timestamp valid_after; + struct GNUNET_TIME_Timestamp signature_validity_start; // valid_after; /** * Tokens signed with this key are valid until this date. */ - struct GNUNET_TIME_Timestamp valid_before; + struct GNUNET_TIME_Timestamp signature_validity_end; // valid_before; + + /** + * Private key expires for use at this time. Signatures can + * only be created until this point. + */ + struct GNUNET_TIME_Timestamp private_key_deleted_at; /** * Token family public key. @@ -1282,9 +1310,22 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails struct TALER_MERCHANTDB_TokenFamilyDetails token_family; }; + +/** + * Function called with applicable token keys. + * + * @param cls closure + * @param details details about an applicable key + */ +typedef void +(*TALER_MERCHANTDB_TokenKeyCallback) ( + void *cls, + const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details); + + /** * Details about a spent token. -*/ + */ struct TALER_MERCHANTDB_SpentTokenDetails { /** @@ -3744,8 +3785,8 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @param instance_id instance to lookup token family key for * @param token_family_slug slug of token family to lookup - * @param min_valid_after lower bound of the start of the key validation period - * @param max_valid_after upper bound of the start of the key validation period + * @param valid_at find a key with a validity period that includes this time + * @param sign_until find a private key that can sign until this time * @param[out] details set to the token family key details on success, can be NULL * (in that case we only want to check if the token family key exists) * @return database result code @@ -3755,12 +3796,35 @@ struct TALER_MERCHANTDB_Plugin void *cls, const char *instance_id, const char *token_family_slug, - struct GNUNET_TIME_Timestamp min_valid_after, - struct GNUNET_TIME_Timestamp max_valid_after, + struct GNUNET_TIME_Timestamp valid_at, + struct GNUNET_TIME_Timestamp sign_until, struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details); /** + * Lookup token family keys that may be used for a payment. + * + * @param cls closure + * @param instance_id instance to lookup token family key for + * @param token_family_slug slug of token family to lookup + * @param start_time signature validity start the keys must fall into + * @param end_time signature validity end the keys must fall into + * @param cb function to call with each matching key + * @param cb_cls closure for @a cb + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*lookup_token_family_keys)( + void *cls, + const char *instance_id, + const char *token_family_slug, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + TALER_MERCHANTDB_TokenKeyCallback cb, + void *cb_cls); + + + /** * Insert details a key pair for a token family. * * @param cls closure @@ -3768,8 +3832,10 @@ struct TALER_MERCHANTDB_Plugin * @param token_family_slug slug of token family to insert the key pair for * @param pub token family public key * @param priv token family private key - * @param valid_after start of the key validation period - * @param valid_before end of the key validation period + * @param key_expires when does the private key expire (because + * the validity period of the next token family key starts) + * @param valid_after start of validity period for signatures with this key + * @param valid_before end of validity period for signatures with this key * @return database result code */ enum GNUNET_DB_QueryStatus @@ -3779,6 +3845,7 @@ struct TALER_MERCHANTDB_Plugin const char *token_family_slug, const struct TALER_TokenIssuePublicKey *pub, const struct TALER_TokenIssuePrivateKey *priv, + struct GNUNET_TIME_Timestamp key_expires, struct GNUNET_TIME_Timestamp valid_after, struct GNUNET_TIME_Timestamp valid_before); diff --git a/src/lib/merchant_api_get_tokenfamily.c b/src/lib/merchant_api_get_tokenfamily.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA + Copyright (C) 2023-2024 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 @@ -104,12 +104,18 @@ handle_get_token_family_finished (void *cls, &res.details.ok.description), GNUNET_JSON_spec_object_const ("description_i18n", &res.details.ok.description_i18n), + GNUNET_JSON_spec_object_const ("extra_data", + &res.details.ok.extra_data), GNUNET_JSON_spec_timestamp ("valid_after", &res.details.ok.valid_after), GNUNET_JSON_spec_timestamp ("valid_before", &res.details.ok.valid_before), GNUNET_JSON_spec_relative_time ("duation", &res.details.ok.duration), + GNUNET_JSON_spec_relative_time ("validity_granularity", + &res.details.ok.validity_granularity), + GNUNET_JSON_spec_relative_time ("start_offset", + &res.details.ok.start_offset), GNUNET_JSON_spec_string ("kind", &res.details.ok.kind), GNUNET_JSON_spec_uint64 ("issued", @@ -155,6 +161,7 @@ handle_get_token_family_finished (void *cls, } } + struct TALER_MERCHANT_TokenFamilyGetHandle * TALER_MERCHANT_token_family_get ( struct GNUNET_CURL_Context *ctx, @@ -199,6 +206,7 @@ TALER_MERCHANT_token_family_get ( return handle; } + void TALER_MERCHANT_token_family_get_cancel ( struct TALER_MERCHANT_TokenFamilyGetHandle *handle) @@ -207,4 +215,4 @@ TALER_MERCHANT_token_family_get_cancel ( GNUNET_CURL_job_cancel (handle->job); GNUNET_free (handle->url); GNUNET_free (handle); -} -\ No newline at end of file +} diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c @@ -520,6 +520,8 @@ TALER_MERCHANT_order_pay_frontend ( &ut->token_sig), GNUNET_JSON_pack_data_auto ("token_pub", &ut->token_pub), + GNUNET_JSON_pack_data_auto ("h_issue", + &ut->issue_pub.public_key->pub_key_hash), TALER_JSON_pack_token_issue_sig ("ub_sig", &ut->ub_sig)); if (0 != diff --git a/src/lib/merchant_api_post_tokenfamilies.c b/src/lib/merchant_api_post_tokenfamilies.c @@ -84,8 +84,8 @@ struct TALER_MERCHANT_TokenFamiliesPostHandle */ static void handle_post_token_families_finished (void *cls, - long response_code, - const void *response) + long response_code, + const void *response) { struct TALER_MERCHANT_TokenFamiliesPostHandle *handle = cls; const json_t *json = response; @@ -98,7 +98,7 @@ handle_post_token_families_finished (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "POST /tokenfamilies completed with response code %u\n", (unsigned int) response_code); - switch (response_code) + switch (response_code) { case 0: hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; @@ -158,6 +158,7 @@ handle_post_token_families_finished (void *cls, TALER_MERCHANT_token_families_post_cancel (handle); } + struct TALER_MERCHANT_TokenFamiliesPostHandle * TALER_MERCHANT_token_families_post ( struct GNUNET_CURL_Context *ctx, @@ -166,10 +167,12 @@ TALER_MERCHANT_token_families_post ( const char *name, const char *description, const json_t *description_i18n, + const json_t *extra_data, struct GNUNET_TIME_Timestamp valid_after, struct GNUNET_TIME_Timestamp valid_before, struct GNUNET_TIME_Relative duration, - struct GNUNET_TIME_Relative rounding, + struct GNUNET_TIME_Relative validity_granularity, + struct GNUNET_TIME_Relative start_offset, const char *kind, TALER_MERCHANT_TokenFamiliesPostCallback cb, void *cb_cls) @@ -188,14 +191,19 @@ TALER_MERCHANT_token_families_post ( GNUNET_JSON_pack_object_incref ("description_i18n", (json_t *) description_i18n)), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("extra_data", + (json_t *) extra_data)), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_timestamp ("valid_after", valid_after)), GNUNET_JSON_pack_timestamp ("valid_before", valid_before), GNUNET_JSON_pack_time_rel ("duration", duration), - GNUNET_JSON_pack_time_rel ("rounding", - rounding), + GNUNET_JSON_pack_time_rel ("validity_granularity", + validity_granularity), + GNUNET_JSON_pack_time_rel ("start_offset", + start_offset), GNUNET_JSON_pack_string ("kind", kind)); handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle); diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -14,11 +14,11 @@ check_SCRIPTS = \ test_merchant_instance_purge.sh \ test_merchant_product_creation.sh \ test_merchant_order_creation.sh \ - test_merchant_transfer_tracking.sh \ test_merchant_kyc.sh \ test_merchant_order_autocleanup.sh \ test_merchant_wirewatch.sh \ - test-merchant-walletharness.sh + test-merchant-walletharness.sh \ + test_merchant_transfer_tracking.sh lib_LTLIBRARIES = \ libtalermerchanttesting.la @@ -118,6 +118,7 @@ TESTS = \ $(check_PROGRAMS) \ $(check_SCRIPTS) +AM_TESTS_ENVIRONMENT=export TALER_MERCHANT_PREFIX=$${TALER_MERCHANT_PREFIX:-@libdir@};export PATH=$${TALER_MERCHANT_PREFIX:-@prefix@}/bin:$$PATH; test_merchant_api_twisted_cs_SOURCES = \ test_merchant_api_twisted.c diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -1697,17 +1697,15 @@ run (void *cls, "An upcoming subscription that is not valid yet.", NULL, /* In one day */ - GNUNET_TIME_absolute_to_timestamp - ( + GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add ( - GNUNET_TIME_timestamp_get () - .abs_time, GNUNET_TIME_UNIT_DAYS)), + GNUNET_TIME_timestamp_get ().abs_time, + GNUNET_TIME_UNIT_DAYS)), /* In a year */ - GNUNET_TIME_absolute_to_timestamp - ( + GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add ( - GNUNET_TIME_timestamp_get () - .abs_time, GNUNET_TIME_UNIT_YEARS)), + GNUNET_TIME_timestamp_get ().abs_time, + GNUNET_TIME_UNIT_YEARS)), GNUNET_TIME_UNIT_MONTHS, GNUNET_TIME_UNIT_MONTHS, "subscription"), @@ -1715,7 +1713,7 @@ run (void *cls, "create-order-with-upcoming-output", cred.cfg, merchant_url, - MHD_HTTP_CONFLICT, + MHD_HTTP_OK, "create-upcoming-tokenfamily", 0, 1, @@ -1723,40 +1721,43 @@ run (void *cls, GNUNET_TIME_UNIT_ZERO_TS, GNUNET_TIME_UNIT_FOREVER_TS, "EUR:5.0"), - TALER_TESTING_cmd_merchant_post_tokenfamilies ("create-tokenfamily", - merchant_url, - MHD_HTTP_NO_CONTENT, - "subscription-1", - "Subscription", - "A subscription.", - NULL, - GNUNET_TIME_UNIT_ZERO_TS, - GNUNET_TIME_relative_to_timestamp - (GNUNET_TIME_UNIT_YEARS), - GNUNET_TIME_UNIT_MONTHS, - GNUNET_TIME_UNIT_MONTHS, - "subscription"), - TALER_TESTING_cmd_merchant_post_orders_choices ("create-order-with-output", - cred.cfg, - merchant_url, - MHD_HTTP_OK, - "create-tokenfamily", - 0, - 1, - "5-output", - GNUNET_TIME_UNIT_ZERO_TS, - GNUNET_TIME_UNIT_FOREVER_TS, - "EUR:5.0"), - TALER_TESTING_cmd_merchant_pay_order_choices ("pay-order-with-output", - merchant_url, - MHD_HTTP_OK, - "create-order-with-output", - "withdraw-coin-1", - "EUR:5", - "EUR:4.99", - NULL, - 0, - NULL), + TALER_TESTING_cmd_merchant_post_tokenfamilies ( + "create-tokenfamily", + merchant_url, + MHD_HTTP_NO_CONTENT, + "subscription-1", + "Subscription", + "A subscription.", + NULL, + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_relative_to_timestamp + (GNUNET_TIME_UNIT_YEARS), + GNUNET_TIME_UNIT_MONTHS, + GNUNET_TIME_UNIT_MONTHS, + "subscription"), + TALER_TESTING_cmd_merchant_post_orders_choices ( + "create-order-with-output", + cred.cfg, + merchant_url, + MHD_HTTP_OK, + "create-tokenfamily", + 0, + 1, + "5-output", + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_FOREVER_TS, + "EUR:5.0"), + TALER_TESTING_cmd_merchant_pay_order_choices ( + "pay-order-with-output", + merchant_url, + MHD_HTTP_OK, + "create-order-with-output", + "withdraw-coin-1", + "EUR:5", + "EUR:4.99", + NULL, + 0, + NULL), TALER_TESTING_cmd_merchant_post_orders_choices ( "create-order-with-input-and-output", cred.cfg, @@ -1818,7 +1819,7 @@ run (void *cls, struct TALER_TESTING_Command donau[] = { TALER_TESTING_cmd_merchant_get_donau_instances( "get-donau-instance", - "http://localhost:8080/donau", + merchant_url, MHD_HTTP_OK), }; diff --git a/src/testing/test_merchant_api.conf b/src/testing/test_merchant_api.conf @@ -83,6 +83,7 @@ WIRE_GATEWAY_AUTH_METHOD = NONE [donau] TERMS_ETAG = tos +CURRENCY = EUR PRIVACY_ETAG = 0 PORT = 8079 DB = postgres diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh @@ -91,6 +91,7 @@ fi # CREATE INSTANCE FOR TESTING # + echo -n "Configuring merchant instance ..." STATUS=$(curl -H "Content-Type: application/json" -X POST \ @@ -135,9 +136,9 @@ then exit_fail "Expected '200 OK' response. Got instead $STATUS" fi - echo "Ok" + echo -n "Get accounts..." STATUS=$(curl http://localhost:9966/private/accounts \ -w "%{http_code}" -s -o "$LAST_RESPONSE") @@ -248,10 +249,12 @@ echo "OK" # CREATE TOKEN FAMILY AND V1 ORDER WITH CHOICES # echo -n "Creating token family ..." -NOW=$(date +%s) -IN_A_YEAR=$((NOW + 31536000)) +export NOW=$(date +%s) +export IN_A_YEAR=$((NOW + 31536000)) +export LAST_RESPONSE + STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \ - -d '{"slug":"test-sub","kind":"subscription","description":"Test token family","name":"Test Subscription","valid_after":{"t_s":'${NOW}'},"valid_before":{"t_s":'${IN_A_YEAR}'},"duration": {"d_us": 2592000000000},"rounding": {"d_us": 86400000000}}' \ + -d '{"slug":"test-sub","kind":"subscription","description":"Test token family","name":"Test Subscription","valid_after":{"t_s":'${NOW}'},"valid_before":{"t_s":'${IN_A_YEAR}'},"duration": {"d_us": 2592000000000},"validity_granularity": {"d_us": 86400000000}}' \ -w "%{http_code}" -s -o "$LAST_RESPONSE") if [ "$STATUS" != "204" ] @@ -262,6 +265,9 @@ fi echo " OK" +echo "curl 'http://localhost:9966/private/orders' \ + -d '{\"order\":{\"version\":1,\"amount\":\"TESTKUDOS:7\",\"summary\":\"with_subscription\",\"fulfillment_message\":\"Paid successfully\",\"choices\":[{\"inputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}],\"outputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}]}]}}'" + echo -n "Creating v1 order with token family ..." STATUS=$(curl 'http://localhost:9966/private/orders' \ -d '{"order":{"version":1,"amount":"TESTKUDOS:7","summary":"with_subscription","fulfillment_message":"Paid successfully","choices":[{"inputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}],"outputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}]}]}}' \ diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -144,14 +144,14 @@ struct PayState * * @param token_families json object of token families where the key is the slug * @param slug the slug of the token family - * @param valid_after the timestamp of the token family + * @param key_index index of the key within the token family * @param[out] pub the token issue public key of the token family * @return #GNUNET_OK on success and #GNUNET_SYSERR if not found */ static enum GNUNET_GenericReturnValue find_token_public_key (const json_t *token_families, const char *slug, - struct GNUNET_TIME_Timestamp valid_after, + unsigned int key_index, struct TALER_TokenIssuePublicKey *pub) { const json_t *tf = json_object_get (token_families, slug); @@ -161,6 +161,14 @@ find_token_public_key (const json_t *token_families, &keys), GNUNET_JSON_spec_end () }; + const json_t *key; + const char *error_name; + unsigned int error_line; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_token_pub ("public_key", + pub), + GNUNET_JSON_spec_end () + }; if (NULL == tf) { @@ -181,77 +189,30 @@ find_token_public_key (const json_t *token_families, return GNUNET_SYSERR; } + key = json_array_get (keys, + key_index); + if (NULL == key) { - unsigned int i; - const json_t *key; - - json_array_foreach (keys, i, key) - { - int64_t cipher; - struct GNUNET_TIME_Timestamp ivalid_after; - struct GNUNET_CRYPTO_BlindSignPublicKey *issue_pub = GNUNET_new (struct - GNUNET_CRYPTO_BlindSignPublicKey); - const char *error_name; - unsigned int error_line; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("h_pub", - &issue_pub->pub_key_hash), - GNUNET_JSON_spec_rsa_public_key ("rsa_pub", - &issue_pub->details.rsa_public_key), - // GNUNET_JSON_spec_fixed_auto ("cs_pub", - // &key.pub.public_key->details.cs_public_key)), - GNUNET_JSON_spec_int64 ("cipher", - &cipher), - GNUNET_JSON_spec_timestamp ("valid_after", - &ivalid_after), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (key, - ispec, - &error_name, - &error_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[error_line].field, - error_line, - error_name); - return GNUNET_SYSERR; - } - - switch (cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - issue_pub->cipher = GNUNET_CRYPTO_BSA_RSA; - break; - case GNUNET_CRYPTO_BSA_CS: - issue_pub->cipher = GNUNET_CRYPTO_BSA_CS; - break; - case GNUNET_CRYPTO_BSA_INVALID: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'cipher' invalid in key #%u\n", - i); - return GNUNET_SYSERR; - } - - /* Compare valid_after to make sure it matches. */ - if (GNUNET_TIME_timestamp_cmp (valid_after, !=, ivalid_after)) - { - continue; - } - - pub->public_key = issue_pub; - return GNUNET_OK; - } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Key with index %u for token family '%s' not found\n", + key_index, + slug); + return GNUNET_SYSERR; } - - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Key with valid_after '%s' for token family '%s' not found\n", - GNUNET_TIME_timestamp2s (valid_after), - slug); - return GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_JSON_parse (key, + ispec, + &error_name, + &error_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + ispec[error_line].field, + error_line, + error_name); + return GNUNET_SYSERR; + } + return GNUNET_OK; } @@ -772,7 +733,7 @@ pay_run (void *cls, { const char *slug; const char *kind; - struct GNUNET_TIME_Timestamp valid_after; + uint32_t key_index; uint32_t count = 1; const char *ierror_name = NULL; unsigned int ierror_line = 0; @@ -782,8 +743,8 @@ pay_run (void *cls, &kind), GNUNET_JSON_spec_string ("token_family_slug", &slug), - GNUNET_JSON_spec_timestamp ("valid_after", - &valid_after), + GNUNET_JSON_spec_uint32 ("key_index", + &key_index), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("count", &count), @@ -820,17 +781,18 @@ pay_run (void *cls, struct TALER_MERCHANT_PrivateTokenDetails *details = &ps->issued_tokens[ps->num_issued_tokens - count + k]; - if (GNUNET_OK != find_token_public_key (token_families, - slug, - valid_after, - &details->issue_pub)) + if (GNUNET_OK != + find_token_public_key (token_families, + slug, + key_index, + &details->issue_pub)) { TALER_TESTING_FAIL (is); } /* Only RSA is supported for now. */ - GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == details->issue_pub.public_key-> - cipher); + GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == + details->issue_pub.public_key->cipher); TALER_token_blind_input_copy (&details->blinding_inputs, TALER_token_blind_input_rsa_singleton ()); @@ -883,29 +845,29 @@ pay_run (void *cls, &h_proposal)) TALER_TESTING_FAIL (is); ps->h_contract_terms = *h_proposal; - ps->oph = TALER_MERCHANT_order_pay (TALER_TESTING_interpreter_get_context ( - is), - ps->merchant_url, - ps->session_id, - h_proposal, - ps->choice_index, - &ps->total_amount, - &max_fee, - &merchant_pub, - merchant_sig, - timestamp, - refund_deadline, - pay_deadline, - &h_wire, - order_id, - npay_coins, - pay_coins, - len_use_tokens, - use_tokens, - len_output_tokens, - output_tokens, - &pay_cb, - ps); + ps->oph = TALER_MERCHANT_order_pay ( + TALER_TESTING_interpreter_get_context (is), + ps->merchant_url, + ps->session_id, + h_proposal, + ps->choice_index, + &ps->total_amount, + &max_fee, + &merchant_pub, + merchant_sig, + timestamp, + refund_deadline, + pay_deadline, + &h_wire, + order_id, + npay_coins, + pay_coins, + len_use_tokens, + use_tokens, + len_output_tokens, + output_tokens, + &pay_cb, + ps); GNUNET_array_grow (pay_coins, npay_coins, 0); diff --git a/src/testing/testing_api_cmd_post_tokenfamilies.c b/src/testing/testing_api_cmd_post_tokenfamilies.c @@ -145,6 +145,7 @@ post_tokenfamilies_cb (void *cls, TALER_TESTING_interpreter_next (state->is); } + /** * Run the "POST /tokenfamilies" CMD. * @@ -168,16 +169,19 @@ post_tokenfamilies_run (void *cls, state->name, state->description, state->description_i18n, + NULL, /* extra data */ state->valid_after, state->valid_before, state->duration, state->rounding, + GNUNET_TIME_UNIT_ZERO, /* start_offset */ state->kind, &post_tokenfamilies_cb, state); GNUNET_assert (NULL != state->handle); } + /** * Offers information from the "POST /tokenfamilies" CMD state to other * commands. @@ -212,6 +216,7 @@ post_tokenfamilies_traits (void *cls, index); } + /** * Free the state of a "POST /tokenfamilies" CMD, and possibly * cancel a pending operation thereof. @@ -235,6 +240,7 @@ post_tokenfamilies_cleanup (void *cls, GNUNET_free (state); } + struct TALER_TESTING_Command TALER_TESTING_cmd_merchant_post_tokenfamilies ( const char *label, @@ -277,4 +283,4 @@ TALER_TESTING_cmd_merchant_post_tokenfamilies ( return cmd; } -} -\ No newline at end of file +}