[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant] branch master updated: fixing various design issues in
From: |
gnunet |
Subject: |
[taler-merchant] branch master updated: fixing various design issues in token logic |
Date: |
Tue, 10 Dec 2024 22:33:20 +0100 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository merchant.
The following commit(s) were added to refs/heads/master by this push:
new 56fd5686 fixing various design issues in token logic
56fd5686 is described below
commit 56fd568628dae06191ccb2133a2ff6b8c092be41
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Dec 10 22:33:12 2024 +0100
fixing various design issues in token logic
---
src/backend/taler-merchant-httpd_contract.c | 109 +--
src/backend/taler-merchant-httpd_contract.h | 118 +--
.../taler-merchant-httpd_post-orders-ID-pay.c | 252 ++++--
...erchant-httpd_private-get-token-families-SLUG.c | 43 +-
...chant-httpd_private-patch-token-families-SLUG.c | 32 +-
.../taler-merchant-httpd_private-post-instances.c | 5 +
.../taler-merchant-httpd_private-post-orders.c | 990 +++++++++++++--------
...er-merchant-httpd_private-post-token-families.c | 198 +++--
src/backenddb/Makefile.am | 1 +
src/backenddb/merchant-0008.sql | 8 +-
src/backenddb/merchant-0013.sql | 40 +-
src/backenddb/pg_insert_token_family.c | 29 +-
src/backenddb/pg_insert_token_family_key.c | 46 +-
src/backenddb/pg_insert_token_family_key.h | 11 +-
src/backenddb/pg_lookup_token_family.c | 26 +-
src/backenddb/pg_lookup_token_family_key.c | 58 +-
src/backenddb/pg_lookup_token_family_key.h | 17 +-
src/backenddb/pg_lookup_token_family_keys.c | 225 +++++
..._family_key.h => pg_lookup_token_family_keys.h} | 37 +-
src/backenddb/pg_update_token_family.c | 20 +-
src/backenddb/plugin_merchantdb_postgres.c | 3 +
src/include/taler_merchant_service.h | 30 +-
src/include/taler_merchantdb_plugin.h | 89 +-
src/lib/merchant_api_get_tokenfamily.c | 12 +-
src/lib/merchant_api_post_order_pay.c | 2 +
src/lib/merchant_api_post_tokenfamilies.c | 20 +-
src/testing/Makefile.am | 5 +-
src/testing/test_merchant_api.c | 87 +-
src/testing/test_merchant_order_creation.sh | 14 +-
src/testing/testing_api_cmd_pay_order.c | 170 ++--
src/testing/testing_api_cmd_post_tokenfamilies.c | 8 +-
31 files changed, 1740 insertions(+), 965 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_contract.c
b/src/backend/taler-merchant-httpd_contract.c
index 07db6a9d..d311990a 100644
--- 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
index 3036dc35..36f3e9d7 100644
--- 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
index 640731e6..5dece35d 100644
--- 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,
@@ -2615,6 +2637,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'
@@ -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
index c2d3a58c..5fb2c84f 100644
--- 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
index 56235514..80547e02 100644
--- 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
index f56e7098..2bd5034d 100644
--- 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
index 4efc1057..3aa108fd 100644
--- 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),
+ ¬s),
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
index 1fbbc44e..b9100522 100644
--- 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) );
}
@@ -88,6 +97,10 @@ TMH_private_post_token_families (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_mark_optional (
GNUNET_JSON_spec_timestamp ("valid_after",
&details.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
index b5a8d1fe..9b18519a 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -127,6 +127,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
index 4dec99d5..2a469fab 100644
--- 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
index 1e43de92..c5805505 100644
--- 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
index 81a1890b..c416b60e 100644
--- 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
index 1f441e49..15b57834 100644
--- 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
index c8c85351..01910beb 100644
--- 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
index 943e0ee7..a489160d 100644
--- 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
index 61ae93fb..153c92be 100644
--- 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
index fd88887c..c6828ffa 100644
--- 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
new file mode 100644
index 00000000..6136bf2c
--- /dev/null
+++ 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_insert_token_family_key.h
b/src/backenddb/pg_lookup_token_family_keys.h
similarity index 53%
copy from src/backenddb/pg_insert_token_family_key.h
copy to src/backenddb/pg_lookup_token_family_keys.h
index c8c85351..bd091429 100644
--- a/src/backenddb/pg_insert_token_family_key.h
+++ b/src/backenddb/pg_lookup_token_family_keys.h
@@ -14,12 +14,12 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backenddb/pg_insert_token_family_key.h
- * @brief implementation of the insert_token_family_key function for Postgres
- * @author Christian Blättler
+ * @file backenddb/pg_lookup_token_family_keys.h
+ * @brief implementation of the lookup_token_family_keys function for Postgres
+ * @author Christian Grothoff
*/
-#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
-#define PG_INSERT_TOKEN_FAMILY_KEY_H
+#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>
@@ -27,25 +27,26 @@
/**
- * Insert new key pair for a token family.
+ * Lookup token family keys that may be used for a payment.
*
* @param cls closure
- * @param merchant_id instance name
- * @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 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_insert_token_family_key (
+TMH_PG_lookup_token_family_keys (
void *cls,
- const char *merchant_id,
+ const char *instance_id,
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 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
index 7864c60b..ea7a122f 100644
--- 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
index 59e3f2fb..21d265d4 100644
--- 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"
@@ -551,6 +552,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
index a97c1eb7..b4f351e7 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -1980,6 +1980,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.
*/
@@ -1995,6 +2000,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".
*/
@@ -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
index 60590047..5951758c 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1209,6 +1209,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.
*/
@@ -1220,14 +1234,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.
@@ -1253,13 +1274,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.
@@ -1277,9 +1305,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
{
/**
@@ -3739,8 +3780,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
@@ -3750,11 +3791,34 @@ 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.
*
@@ -3763,8 +3827,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
@@ -3774,6 +3840,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
index 232d30d6..6c42a4e4 100644
--- 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
index d10fc6d7..1996f757 100644
--- 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
index 95cbd13b..d2c1565b 100644
--- 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)
@@ -187,6 +190,9 @@ TALER_MERCHANT_token_families_post (
GNUNET_JSON_pack_allow_null (
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)),
@@ -194,8 +200,10 @@ TALER_MERCHANT_token_families_post (
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
index b087f070..b0f7aca9 100644
--- 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
@@ -117,6 +117,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
index 483aacce..24118031 100644
--- 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,
diff --git a/src/testing/test_merchant_order_creation.sh
b/src/testing/test_merchant_order_creation.sh
index a566cb7d..d55f7ce1 100755
--- 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
index dc27b842..49dd7e81 100644
--- 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
index c24b98db..92ed2c77 100644
--- 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
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant] branch master updated: fixing various design issues in token logic,
gnunet <=