aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/taler-merchant-httpd.c4
-rw-r--r--src/backend/taler-merchant-httpd_contract.c415
-rw-r--r--src/backend/taler-merchant-httpd_contract.h184
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c949
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-orders-ID.c5
-rw-r--r--src/backend/taler-merchant-httpd_private-get-orders-ID.c26
-rw-r--r--src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c7
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c649
-rw-r--r--src/backend/taler-merchant-httpd_private-post-token-families.c35
9 files changed, 1957 insertions, 317 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index e9ee1cbf..a7c1cbb4 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -2264,6 +2264,10 @@ run (void *cls,
return;
}
+ /* TODO: Load config variables for merchant token family
+ cipher type "rsa" or "cs" and key size.
+ Defaults to "rsa" and 2048 bits. */
+
if (GNUNET_OK !=
TALER_CONFIG_parse_currencies (cfg,
&TMH_num_cspecs,
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
index 38c82e70..e7c7ac49 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -19,11 +19,13 @@
* @author Christian Blättler
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <jansson.h>
+#include <stdint.h>
#include "taler-merchant-httpd_contract.h"
enum TALER_MerchantContractInputType
-TMH_string_to_contract_input_type (const char *str)
+TMH_contract_input_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
@@ -35,7 +37,7 @@ TMH_string_to_contract_input_type (const char *str)
}
enum TALER_MerchantContractOutputType
-TMH_string_to_contract_output_type (const char *str)
+TMH_contract_output_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
@@ -45,3 +47,412 @@ TMH_string_to_contract_output_type (const char *str)
return TALER_MCOT_INVALID;
}
+
+const char *
+TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t)
+{
+ switch (t) {
+ case TALER_MCIT_TOKEN:
+ return "token";
+ case TALER_MCIT_COIN:
+ return "coin";
+ default:
+ return "invalid";
+ }
+}
+
+const char *
+TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t)
+{
+ switch (t) {
+ case TALER_MCOT_TOKEN:
+ return "token";
+ case TALER_MCOT_COIN:
+ return "coin";
+ case TALER_MCOT_TAX_RECEIPT:
+ return "tax_receipt";
+ default:
+ return "invalid";
+ }
+}
+
+/**
+ * Parse given JSON object to choices array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json array representing the choices
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_choices (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_MerchantContractChoice **choices = spec->ptr;
+ unsigned int *choices_len = cls;
+ if (!json_is_array (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (*choices,
+ *choices_len,
+ json_array_size (root));
+
+ for (unsigned int i = 0; i<*choices_len; i++)
+ {
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *error_name;
+ unsigned int error_line;
+ struct TALER_MerchantContractChoice *choice = &(*choices)[i];
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (root, i),
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ 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),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jinput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ input.type = TMH_contract_input_type_from_string (kind);
+
+ if (TALER_MCIT_INVALID == input.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in input #%u\n",
+ (unsigned int) idx);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ GNUNET_array_append (choice->inputs,
+ choice->inputs_len,
+ input);
+ }
+ }
+
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = {.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",
+ &output.details.token.token_family_slug),
+ 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_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ output.type = TMH_contract_output_type_from_string (kind);
+
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in output #%u\n",
+ (unsigned int) idx);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ GNUNET_array_append (choice->outputs,
+ choice->outputs_len,
+ output);
+ }
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_choices (const char *name,
+ struct TALER_MerchantContractChoice **choices,
+ unsigned int *choices_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) choices_len,
+ .parser = &parse_choices,
+ .field = name,
+ .ptr = choices,
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to token families array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json object representing the token families. The keys are
+ * the token family slugs.
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_token_families (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_MerchantContractTokenFamily **families = spec->ptr;
+ unsigned int *families_len = cls;
+ json_t *jfamily;
+ const char *slug;
+ if (!json_is_object (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ json_object_foreach(root, slug, jfamily)
+ {
+ const json_t *keys;
+ struct TALER_MerchantContractTokenFamily family = {
+ .slug = 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;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jfamily,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ 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;
+ 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),
+ // 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) {
+ 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);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_array_append (*families, *families_len, family);
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_token_families (const char *name,
+ struct TALER_MerchantContractTokenFamily **families,
+ unsigned int *families_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) families_len,
+ .parser = &parse_token_families,
+ .field = name,
+ .ptr = families,
+ };
+
+ return ret;
+}
+
+enum GNUNET_GenericReturnValue
+TMH_find_token_family_key (const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_MerchantContractTokenFamily *families,
+ unsigned int families_len,
+ struct TALER_MerchantContractTokenFamily *family,
+ struct TALER_MerchantContractTokenFamilyKey *key)
+{
+ for (unsigned int i = 0; i < families_len; i++)
+ {
+ if (0 != strcmp (families[i].slug, slug))
+ {
+ continue;
+ }
+ if (NULL != family)
+ {
+ *family = families[i];
+ }
+ for (unsigned int k = 0; k < family->keys_len; k++)
+ {
+ if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
+ ==,
+ valid_after))
+ {
+ if (NULL != key)
+ {
+ *key = family->keys[k];
+ }
+ return GNUNET_OK;
+ }
+ }
+ /* matching family found, but no key. */
+ return GNUNET_NO;
+ }
+
+ /* no matching family found */
+ return GNUNET_NO;
+} \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
index b1e3938c..6389cc70 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -19,6 +19,7 @@
* @author Christian Blättler
*/
#include "taler-merchant-httpd.h"
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
@@ -251,102 +252,92 @@ struct TALER_MerchantContractChoice
unsigned int outputs_len;
};
-struct TALER_MerchantContractLimits
+/**
+ * Public key and corresponding metadata for a token family.
+ */
+struct TALER_MerchantContractTokenFamilyKey
{
/**
- * Currency these limits are for.
+ * Public key.
*/
- char currency[TALER_CURRENCY_LEN];
+ struct TALER_TokenIssuePublicKeyP pub;
/**
- * The hash of the merchant instance's wire details.
+ * Tokens signed by this key will be valid after this time.
*/
- struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp valid_after;
/**
- * Wire transfer method identifier for the wire method associated with ``h_wire``.
- * The wallet may only select exchanges via a matching auditor if the
- * exchange also supports this wire method.
- * The wire transfer fees must be added based on this wire transfer method.
+ * Tokens signed by this key will be valid before this time.
*/
- char *wire_method;
+ struct GNUNET_TIME_Timestamp valid_before;
+};
+struct TALER_MerchantContractTokenFamily
+{
/**
- * Maximum total deposit fee accepted by the merchant for this contract.
+ * Slug of the token family.
*/
- struct TALER_Amount max_fee;
-};
+ const char *slug;
-struct TALER_MerchantContractTokenAuthority
-{
/**
- * Label of the token authority.
- */
- const char *label;
+ * Human-readable name of the token family.
+ */
+ char *name;
/**
- * Human-readable description of the semantics of the tokens issued by
- * this authority.
- */
+ * Human-readable description of the semantics of the tokens issued by
+ * this token family.
+ */
char *description;
/**
- * Map from IETF BCP 47 language tags to localized description.
- */
+ * Map from IETF BCP 47 language tags to localized description.
+ */
json_t *description_i18n;
/**
- * Public key used to validate tokens signed by this authority.
- */
- struct TALER_TokenFamilyPublicKey *pub;
+ * Relevant public keys of this token family for the given contract.
+ */
+ struct TALER_MerchantContractTokenFamilyKey *keys;
/**
- * When will tokens signed by this key expire?
- */
- struct GNUNET_TIME_Timestamp token_expiration;
+ * Length of the @e keys array.
+ */
+ unsigned int keys_len;
/**
- * Must a wallet understand this token type to process contracts that
- * consume or yield it?
- */
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
+ */
bool critical;
/**
- * Kind of the token.
- */
+ * Kind of the token family.
+ */
enum TALER_MerchantContractTokenKind kind;
/**
- * Kind-specific information about the token.
- */
+ * Kind-specific information about the token.
+ */
union
{
/**
- * Subscription token.
- */
+ * Subscription token.
+ */
struct
{
/**
- * When does the subscription period start?
- */
- struct GNUNET_TIME_Timestamp start_date;
-
- /**
- * When does the subscription period end?
- */
- struct GNUNET_TIME_Timestamp end_date;
-
- /**
- * Array of domain names where this subscription can be safely used
- * (e.g. the issuer warrants that these sites will re-issue tokens of
- * this type if the respective contract says so). May contain "*" for
- * any domain or subdomain.
- */
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
+ */
const char **trusted_domains;
/**
- * Length of the @e trusted_domains array.
- */
+ * Length of the @e trusted_domains array.
+ */
unsigned int trusted_domains_len;
} subscription;
@@ -356,19 +347,19 @@ struct TALER_MerchantContractTokenAuthority
struct
{
/**
- * Array of domain names where this discount token is intended to be
- * used. May contain "*" for any domain or subdomain. Users should be
- * warned about sites proposing to consume discount tokens of this
- * type that are not in this list that the merchant is accepting a
- * coupon from a competitor and thus may be attaching different
- * semantics (like get 20% discount for my competitors 30% discount
- * token).
- */
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
+ */
const char **expected_domains;
/**
- * Length of the @e expected_domains array.
- */
+ * Length of the @e expected_domains array.
+ */
unsigned int expected_domains_len;
} discount;
@@ -540,7 +531,7 @@ struct TALER_MerchantContract
/**
* Array of token authorities.
*/
- struct TALER_MerchantContractTokenAuthority *token_authorities;
+ struct TALER_MerchantContractTokenFamily *token_authorities;
/**
* Length of the @e token_authorities array.
@@ -556,10 +547,16 @@ struct TALER_MerchantContract
};
enum TALER_MerchantContractInputType
-TMH_string_to_contract_input_type (const char *str);
+TMH_contract_input_type_from_string (const char *str);
enum TALER_MerchantContractOutputType
-TMH_string_to_contract_output_type (const char *str);
+TMH_contract_output_type_from_string (const char *str);
+
+const char *
+TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t);
+
+const char *
+TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t);
/**
* Serialize @a contract to a JSON object, ready to be stored in the database.
@@ -589,4 +586,53 @@ enum GNUNET_GenericReturnValue
TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
const struct TMH_MerchantInstance *instance,
json_t *exchanges,
- json_t **out); \ No newline at end of file
+ json_t **out);
+
+/**
+ * Provide specification to parse given JSON array to an array
+ * 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
+ * @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);
+
+/**
+ * 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
+ * @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);
+
+
+/**
+ * Find matching token family in @a families based on @a slug. Then use
+ * @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 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
+ */
+enum GNUNET_GenericReturnValue
+TMH_find_token_family_key (const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_MerchantContractTokenFamily *families,
+ unsigned int families_len,
+ struct TALER_MerchantContractTokenFamily *family,
+ struct TALER_MerchantContractTokenFamilyKey *key); \ No newline at end of file
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 14edfd55..888ea0ba 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -25,14 +25,27 @@
* @author Florian Dold
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
#include <taler/taler_dbevents.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -46,6 +59,16 @@
#define MAX_COIN_ALLOWED_COINS 1024
/**
+ * Maximum number of tokens that we allow as inputs per transaction
+ */
+#define MAX_TOKEN_ALLOWED_INPUTs 128
+
+/**
+ * Maximum number of tokens that we allow as outputs per transaction
+ */
+#define MAX_TOKEN_ALLOWED_OUTPUTs 128
+
+/**
* How often do we ask the exchange again about our
* KYC status? Very rarely, as if the user actively
* changes it, we should usually notice anyway.
@@ -69,11 +92,21 @@ enum PayPhase
PP_INIT = 0,
/**
+ * Parse wallet data object from the pay request.
+ */
+ PP_PARSE_WALLET_DATA,
+
+ /**
* Check database state for the given order.
*/
PP_CHECK_CONTRACT,
/**
+ * Validate provided tokens and token evelopes.
+ */
+ PP_VALIDATE_TOKENS,
+
+ /**
* Contract has been paid.
*/
PP_CONTRACT_PAID,
@@ -202,6 +235,75 @@ struct DepositConfirmation
};
+struct TokenUseConfirmation
+{
+
+ /**
+ * Signature on the deposit request made using the token use private key.
+ */
+ struct TALER_TokenUseSignatureP sig;
+
+ /**
+ * Token use public key. This key was blindly signed by the merchant during
+ * the token issuance process.
+ */
+ struct TALER_TokenUsePublicKeyP pub;
+
+ /**
+ * Unblinded signature on the token use public key done by the merchant.
+ */
+ struct TALER_TokenIssueSignatureP unblinded_sig;
+
+ /**
+ * Hash of the token issue public key associated with this token.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+ /**
+ * true if we found this token in the database.
+ */
+ bool found_in_db;
+
+};
+
+
+/**
+ * Information about a token envelope.
+ */
+struct TokenEnvelope
+{
+
+ /**
+ * Blinded token use public keys waiting to be signed.
+ */
+ struct TALER_TokenEnvelopeP blinded_token;
+
+ /**
+ * Hash of token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+};
+
+
+/**
+ * (Blindly) signed token to be returned to the wallet.
+ */
+struct SignedOutputToken
+{
+
+ /**
+ * Blinded token use public keys waiting to be signed.
+ */
+ struct TALER_TokenIssueBlindSignatureP sig;
+
+ /**
+ * Hash of token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+};
+
/**
* Information kept during a pay request for each exchange.
@@ -274,6 +376,32 @@ struct PayContext
struct DepositConfirmation *dc;
/**
+ * Array with @e tokens_cnt input tokens passed to this request.
+ */
+ struct TokenUseConfirmation *tokens;
+
+ /**
+ * Array with @e output_tokens_cnt signed tokens returned in
+ * the response to the wallet.
+ */
+ struct SignedOutputToken *output_tokens;
+
+ /**
+ * Array with @e token_envelopes_cnt (blinded) token envelopes.
+ */
+ struct TokenEnvelope *token_envelopes;
+
+ /**
+ * Array with @e choices_len choices from the contract terms.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Array with @e token_families_len token families from the contract terms.
+ */
+ struct TALER_MerchantContractTokenFamily *token_families;
+
+ /**
* MHD connection to return to
*/
struct MHD_Connection *connection;
@@ -302,11 +430,32 @@ struct PayContext
struct MHD_Response *response;
/**
+ * Index of selected choice in the @e contract_terms choices array.
+ */
+ int64_t choice_index;
+
+ /**
* Our contract (or NULL if not available).
*/
json_t *contract_terms;
/**
+ * Wallet data json object from the request. Containing additional
+ * wallet data such as the selected choice_index.
+ */
+ const json_t *wallet_data;
+
+ /**
+ * Hash of the canonicalized wallet data json object.
+ */
+ struct GNUNET_HashCode h_wallet_data;
+
+ /**
+ * Output commitment hash calculated from the 'tokens_evs' field of the request.
+ */
+ struct GNUNET_HashCode h_outputs;
+
+ /**
* Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
*/
void *json_parse_context;
@@ -421,6 +570,34 @@ struct PayContext
size_t coins_cnt;
/**
+ * Number of input tokens passed to this request. Length
+ * of the @e tokens array.
+ */
+ size_t tokens_cnt;
+
+ /**
+ * Number of token envelopes passed to this request.
+ * Length of the @e token_envelopes array.
+ */
+ size_t token_envelopes_cnt;
+
+ /**
+ * Number of output tokens to return in the response.
+ * Length of the @e output_tokens array.
+ */
+ unsigned int output_tokens_len;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Length of the @e token_families array.
+ */
+ unsigned int token_families_len;
+
+ /**
* Number of exchanges involved in the payment. Length
* of the @e eg array.
*/
@@ -1137,6 +1314,7 @@ AGE_FAIL:
.merchant_payto_uri = pc->wm->payto_uri,
.wire_salt = pc->wm->wire_salt,
.h_contract_terms = pc->h_contract_terms,
+ .wallet_data_hash = pc->h_wallet_data,
.wallet_timestamp = pc->timestamp,
.merchant_pub = hc->instance->merchant_pub,
.refund_deadline = pc->refund_deadline
@@ -1325,6 +1503,28 @@ phase_batch_deposits (struct PayContext *pc)
/**
+ * Build JSON array of blindly signed token envelopes,
+ * to be used in the response to the wallet.
+ *
+ * @param[in,out] pc payment context to use
+ */
+static json_t *
+build_token_sigs (struct PayContext *pc)
+{
+ json_t *token_sigs = json_array ();
+ for (unsigned int i = 0; i < pc->output_tokens_len; i++)
+ {
+ json_array_append_new (token_sigs, GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_blinded_sig ("blind_sig", pc->output_tokens[i].sig.signature),
+ GNUNET_JSON_pack_data_auto ("h_issue", &pc->output_tokens[i].h_issue)
+ ));
+ }
+
+ return token_sigs;
+}
+
+
+/**
* Generate response (payment successful)
*
* @param[in,out] pc payment context where the payment was successful
@@ -1334,6 +1534,7 @@ phase_success_response (struct PayContext *pc)
{
struct TALER_MerchantSignatureP sig;
char *pos_confirmation;
+ json_t *token_sigs;
/* Sign on our end (as the payment did go through, even if it may
have been refunded already) */
@@ -1347,6 +1548,9 @@ phase_success_response (struct PayContext *pc)
pc->pos_alg,
&pc->amount,
pc->timestamp);
+ token_sigs = (0 >= pc->output_tokens_len)
+ ? NULL
+ : build_token_sigs (pc);
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -1354,6 +1558,9 @@ phase_success_response (struct PayContext *pc)
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("pos_confirmation",
pos_confirmation)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("token_sigs",
+ token_sigs)),
GNUNET_JSON_pack_data_auto ("sig",
&sig)));
GNUNET_free (pos_confirmation);
@@ -1851,6 +2058,47 @@ phase_execute_pay_transaction (struct PayContext *pc)
return;
}
+ for (size_t i = 0; i<pc->tokens_cnt; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Insert used token into database, the unique contraint will
+ case an error if this token was used before. */
+ qs = TMH_db->insert_spent_token (TMH_db->cls,
+ &pc->h_contract_terms,
+ &tuc->h_issue,
+ &tuc->pub,
+ &tuc->sig,
+ &tuc->unblinded_sig);
+
+ if (0 > qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return; /* do it again */
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert used token"));
+ return;
+ }
+ else if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* UNIQUE constraint violation, meaning this token was already used. */
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID,
+ NULL));
+ return;
+ }
+ }
+
{
enum GNUNET_DB_QueryStatus qs;
@@ -1968,6 +2216,34 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
}
+ /* Store signed output tokens in database. */
+ for (size_t i = 0; i<pc->output_tokens_len; i++)
+ {
+ struct SignedOutputToken *output = &pc->output_tokens[i];
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->insert_issued_token (TMH_db->cls,
+ &pc->h_contract_terms,
+ &output->h_issue,
+ &output->sig);
+
+ if (0 >= qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return; /* do it again */
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert output token"));
+ return;
+ }
+ }
+
TMH_notify_order_change (hc->instance,
TMH_OSF_CLAIMED | TMH_OSF_PAID,
pc->timestamp,
@@ -2026,6 +2302,342 @@ 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.
+ */
+static enum GNUNET_GenericReturnValue
+find_valid_input_tokens (struct PayContext *pc,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ unsigned int expected_num)
+{
+ int num_validated = 0;
+ struct TokenUseConfirmation *tuc = NULL;
+
+ for (size_t i = 0; i < pc->tokens_cnt; i++)
+ {
+ if (0 != GNUNET_CRYPTO_hash_cmp (&pc->tokens[i].h_issue.hash,
+ &key->pub.public_key->pub_key_hash))
+ {
+ continue;
+ }
+
+ tuc = &pc->tokens[i];
+ if (NULL == tuc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with "
+ "valid_after `%s' not found\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ 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"));
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK != TALER_token_issue_verify (&tuc->pub,
+ &key->pub,
+ &tuc->unblinded_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with valid_after "
+ "`%s' has invalid issue signature\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ISSUE_SIG_INVALID,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK != TALER_wallet_token_use_verify (&pc->h_contract_terms,
+ &pc->h_wallet_data,
+ &tuc->pub,
+ &tuc->sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with valid_after "
+ "`%s' has invalid use signature\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_USE_SIG_INVALID,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ num_validated++;
+ }
+
+ 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_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_validated);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_COUNT_MISMATCH,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ return GNUNET_YES;
+}
+
+
+static enum GNUNET_GenericReturnValue
+sign_token_envelopes (struct PayContext *pc,
+ const char *token_family_slug,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ struct TALER_TokenIssuePrivateKeyP *priv,
+ unsigned int expected_num)
+{
+ int num_signed = 0;
+
+ for (unsigned int i = 0; i<pc->token_envelopes_cnt; i++)
+ {
+ if (0 != GNUNET_CRYPTO_hash_cmp (&pc->token_envelopes[i].h_issue.hash,
+ &key->pub.public_key->pub_key_hash))
+ {
+ continue;
+ }
+
+ TALER_token_issue_sign (priv,
+ &pc->token_envelopes[i].blinded_token,
+ &pc->output_tokens[i].sig);
+
+ pc->output_tokens[i].h_issue.hash = pc->token_envelopes[i].h_issue.hash;
+
+ num_signed++;
+ }
+
+ if (num_signed != expected_num)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected %d token envelopes for public key with valid_after "
+ "'%s', but found %d\n",
+ expected_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_signed);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ENVELOPE_COUNT_MISMATCH,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Validate tokens and token envelopes. First, we check if all tokens listed in
+ * the 'inputs' array of the selected choice are present in the 'tokens' array
+ * of the request. Then, we validate the signatures of each provided token.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_validate_tokens (struct PayContext *pc)
+{
+ if (NULL == pc->choices || 0 >= pc->choices_len)
+ {
+ /* No tokens to validate */
+ pc->phase = PP_PAY_TRANSACTION;
+ return;
+ }
+
+ if (pc->choice_index < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has non-empty choices array but"
+ "request is missing 'choice_index' field\n",
+ pc->order_id);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISSING,
+ NULL));
+ return;
+ }
+
+ if (pc->choice_index >= pc->choices_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has choices array with %u elements but "
+ "request has 'choice_index' field with value %ld\n",
+ pc->order_id,
+ pc->choices_len,
+ pc->choice_index);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS,
+ NULL));
+ return;
+ }
+
+ {
+ struct TALER_MerchantContractChoice selected;
+
+ selected = pc->choices[pc->choice_index];
+
+ /* 1. Iterate over inputs of selected choice:
+ 1.1. Get key for each input.
+ 1.2. Check if token signed by this key are valid at the current time.
+ 1.3. Iterate over provided tokens and check if required number with matching h_issue are present.
+ 1.4. Validate ub_sig with the issue public key, validate token_sig using the token_pub key of the request.
+ 1.5. Sum up validated tokens and check if validated_len == tokens_cnt after loop. */
+ 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;
+
+ 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))
+ {
+ /* 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));
+ return;
+ }
+
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ /* 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));
+ return;
+ }
+
+ if (GNUNET_NO == find_valid_input_tokens (pc,
+ &key,
+ input.details.token.count))
+ {
+ /* Error is already scheduled from find_valid_input_token. */
+ return;
+ }
+ }
+
+ GNUNET_array_grow (pc->output_tokens,
+ pc->output_tokens_len,
+ selected.outputs_len);
+
+ 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;
+
+ 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))
+ {
+ /* 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));
+ 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 (qs <= 0)
+ {
+ 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));
+ return;
+ }
+
+ GNUNET_assert (NULL != details.priv.private_key);
+
+ if (GNUNET_OK != sign_token_envelopes (pc,
+ family.slug,
+ &key,
+ &details.priv,
+ output.details.token.count))
+ {
+ /* Error is already scheduled from sign_token_envelopes. */
+ return;
+ }
+
+ }
+ }
+
+ pc->phase = PP_PAY_TRANSACTION;
+}
+
+
+/**
* Function called with information about a coin that was deposited.
* Checks if this coin is in our list of deposits as well.
*
@@ -2075,6 +2687,38 @@ deposit_paid_check (
}
+static void
+input_tokens_paid_check (
+ void *cls,
+ uint64_t spent_token_serial,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig)
+{
+ struct PayContext *pc = cls;
+
+ for (size_t i = 0; i<pc->tokens_cnt; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ if ( (0 ==
+ GNUNET_CRYPTO_hash_cmp (&tuc->h_issue.hash,
+ &h_issue_pub->hash)) &&
+ (0 ==
+ GNUNET_memcmp (&tuc->pub, use_pub)) &&
+ (0 ==
+ GNUNET_memcmp (&tuc->sig, use_sig)) &&
+ (0 ==
+ GNUNET_memcmp (&tuc->unblinded_sig, issue_sig)) )
+ {
+ tuc->found_in_db = true;
+ break;
+ }
+ }
+}
+
/**
* Handle case where contract was already paid. Either decides
* the payment is idempotent, or refunds the excess payment.
@@ -2084,31 +2728,64 @@ deposit_paid_check (
static void
phase_contract_paid (struct PayContext *pc)
{
- enum GNUNET_DB_QueryStatus qs;
- bool unmatched = false;
json_t *refunds;
+ bool unmatched = false;
- qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
- pc->order_serial,
- &deposit_paid_check,
- pc);
- if (qs <= 0)
{
- GNUNET_break (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_deposits_by_order"));
- return;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
+ pc->order_serial,
+ &deposit_paid_check,
+ pc);
+ /* Since orders with choices can have a price of zero,
+ 0 is also a valid query state */
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_deposits_by_order"));
+ return;
+ }
}
- for (size_t i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt && !unmatched; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
if (! dci->matched_in_db)
unmatched = true;
}
+ /* Check if provided input tokens match token in the database */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* TODO: Use h_contract instead of order_serial here? */
+ qs = TMH_db->lookup_spent_tokens_by_order (TMH_db->cls,
+ pc->order_serial,
+ &input_tokens_paid_check,
+ pc);
+
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_spent_tokens_by_order"));
+ return;
+ }
+ }
+ for (size_t i = 0; i<pc->tokens_cnt && !unmatched; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ if (! tuc->found_in_db)
+ unmatched = true;
+ }
if (! unmatched)
{
/* Everything fine, idempotent request */
@@ -2120,6 +2797,7 @@ phase_contract_paid (struct PayContext *pc)
TALER_merchant_pay_sign (&pc->h_contract_terms,
&pc->hc->instance->merchant_priv,
&sig);
+ /* TODO: Add token_sigs to response body. */
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -2129,6 +2807,8 @@ phase_contract_paid (struct PayContext *pc)
return;
}
/* Conflict, double-payment detected! */
+ /* TODO: What should we do with input tokens?
+ Currently there is no refund for tokens. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Client attempted to pay extra for already paid order `%s'\n",
pc->order_id);
@@ -2288,6 +2968,16 @@ phase_check_contract (struct PayContext *pc)
&pc->pay_deadline),
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
&pc->wire_transfer_deadline),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_choices ("choices",
+ &pc->choices,
+ &pc->choices_len),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_token_families ("token_families",
+ &pc->token_families,
+ &pc->token_families_len),
+ NULL),
GNUNET_JSON_spec_fixed_auto ("h_wire",
&pc->h_wire),
GNUNET_JSON_spec_mark_optional (
@@ -2390,7 +3080,72 @@ phase_check_contract (struct PayContext *pc)
}
pc->wm = wm;
}
- pc->phase = PP_PAY_TRANSACTION;
+ pc->phase = PP_VALIDATE_TOKENS;
+}
+
+
+/**
+ * Try to parse the wallet_data object of the pay request into
+ * the given context. Schedules an error response in the connection
+ * on failure.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_parse_wallet_data (struct PayContext *pc)
+{
+ struct GNUNET_HashCode h_outputs_req;
+ pc->choice_index = -1;
+
+ // TODO: Ensure that wallet_data must be set for contracts with choices.
+ if (NULL == pc->wallet_data)
+ {
+ pc->phase = PP_CHECK_CONTRACT;
+ return;
+ }
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_int64 ("choice_index",
+ &pc->choice_index),
+ NULL),
+ GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_fixed_auto ("h_outputs",
+ &h_outputs_req),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ pc->wallet_data,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ if (0 != GNUNET_CRYPTO_hash_cmp(&h_outputs_req, &pc->h_outputs))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'wallet_data.h_outputs' does not match hash of tokens_evs"));
+ return;
+ }
+
+ TALER_json_hash (pc->wallet_data,
+ &pc->h_wallet_data);
+
+ pc->phase = PP_CHECK_CONTRACT;
}
@@ -2405,6 +3160,8 @@ phase_parse_pay (struct PayContext *pc)
{
const char *session_id = NULL;
const json_t *coins;
+ const json_t *tokens;
+ const json_t *tokens_evs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("coins",
&coins),
@@ -2412,6 +3169,18 @@ phase_parse_pay (struct PayContext *pc)
GNUNET_JSON_spec_string ("session_id",
&session_id),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("wallet_data",
+ &pc->wallet_data),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("tokens",
+ &tokens),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("tokens_evs",
+ &tokens_evs),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -2443,6 +3212,7 @@ phase_parse_pay (struct PayContext *pc)
/* use empty string as default if client didn't specify it */
pc->session_id = GNUNET_strdup ("");
}
+
pc->coins_cnt = json_array_size (coins);
if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
{
@@ -2455,7 +3225,6 @@ phase_parse_pay (struct PayContext *pc)
"'coins' array too long"));
return;
}
-
/* note: 1 coin = 1 deposit confirmation expected */
pc->dc = GNUNET_new_array (pc->coins_cnt,
struct DepositConfirmation);
@@ -2574,7 +3343,145 @@ phase_parse_pay (struct PayContext *pc)
}
}
}
- pc->phase = PP_CHECK_CONTRACT;
+
+ pc->tokens_cnt = json_array_size (tokens);
+ if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_INPUTs)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'tokens' array too long"));
+ return;
+ }
+
+ pc->tokens = GNUNET_new_array (pc->tokens_cnt,
+ struct TokenUseConfirmation);
+
+ /* This look populates the array 'tokens' in 'pc' */
+ {
+ unsigned int tokens_index;
+ json_t *token;
+
+ json_array_foreach (tokens, tokens_index, token)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[tokens_index];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("token_sig",
+ &tuc->sig),
+ GNUNET_JSON_spec_fixed_auto ("token_pub",
+ &tuc->pub),
+ TALER_JSON_spec_token_issue_sig ("ub_sig",
+ &tuc->unblinded_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_issue",
+ &tuc->h_issue),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ token,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ for (unsigned int j = 0; j<tokens_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (&tuc->pub,
+ &pc->tokens[j].pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate token in list"));
+ return;
+ }
+ }
+ }
+ }
+
+ pc->token_envelopes_cnt = json_array_size (tokens_evs);
+ if (pc->token_envelopes_cnt > MAX_TOKEN_ALLOWED_OUTPUTs)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'tokens_evs' array too long"));
+ return;
+ }
+ if (0 < pc->token_envelopes_cnt)
+ {
+ /* Calculate output commitment to be verified later. */
+ TALER_json_hash (tokens_evs,
+ &pc->h_outputs);
+
+ }
+
+ pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt,
+ struct TokenEnvelope);
+
+ {
+ unsigned int tokens_ev_index;
+ json_t *token_ev;
+
+ json_array_foreach (tokens_evs, tokens_ev_index, token_ev)
+ {
+ struct TokenEnvelope *ev = &pc->token_envelopes[tokens_ev_index];
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_token_envelope ("token_ev",
+ &ev->blinded_token),
+ GNUNET_JSON_spec_fixed_auto ("h_issue",
+ &ev->h_issue.hash),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ token_ev,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ for (unsigned int j = 0; j<tokens_ev_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (ev->blinded_token.blinded_pub,
+ pc->token_envelopes[j].blinded_token.blinded_pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate token envelope in list"));
+ return;
+ }
+ }
+ }
+ }
+ pc->phase = PP_PARSE_WALLET_DATA;
}
@@ -2660,12 +3567,18 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
case PP_INIT:
phase_parse_pay (pc);
break;
+ case PP_PARSE_WALLET_DATA:
+ phase_parse_wallet_data (pc);
+ break;
case PP_CHECK_CONTRACT:
phase_check_contract (pc);
break;
case PP_CONTRACT_PAID:
phase_contract_paid (pc);
break;
+ case PP_VALIDATE_TOKENS:
+ phase_validate_tokens (pc);
+ break;
case PP_PAY_TRANSACTION:
phase_execute_pay_transaction (pc);
break;
diff --git a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
index cd8aa10c..f288ee05 100644
--- a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
@@ -20,6 +20,7 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_private-delete-orders-ID.h"
+#include <stdint.h>
#include <taler/taler_json_lib.h>
@@ -80,6 +81,7 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
bool paid = false;
bool wired = false;
bool matches = false;
+ int16_t choice_index;
qs = TMH_db->lookup_order (TMH_db->cls,
mi->settings.id,
@@ -98,7 +100,8 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
&paid,
&wired,
&matches,
- NULL);
+ NULL,
+ &choice_index);
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return TALER_MHD_reply_with_error (connection,
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 98653997..50626876 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -22,6 +22,9 @@
#include "platform.h"
#include "taler-merchant-httpd_private-get-orders-ID.h"
#include "taler-merchant-httpd_get-orders-ID.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <stdint.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_dbevents.h>
#include "taler-merchant-httpd_mhd.h"
@@ -316,6 +319,12 @@ struct GetOrderRequestContext
uint64_t order_serial;
/**
+ * Index of selected choice from ``choices`` array in the contract_terms.
+ * Is -1 for orders without choices.
+ */
+ int16_t choice_index;
+
+ /**
* Total refunds granted for this payment. Only initialized
* if @e refunded is set to true.
*/
@@ -610,6 +619,7 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc)
gorc->order_only = false;
}
TMH_db->preflight (TMH_db->cls);
+ /* TODO: Check if choice_index is actually set to NULL if not in db. */
qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
hc->instance->settings.id,
hc->infix,
@@ -619,7 +629,8 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc)
&gorc->paid,
&gorc->wired,
&gorc->paid_session_matches,
- &gorc->claim_token);
+ &gorc->claim_token,
+ &gorc->choice_index);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"lookup_contract_terms (%s) returned %d\n",
hc->infix,
@@ -1376,6 +1387,7 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
struct TMH_HandlerContext *hc = gorc->hc;
MHD_RESULT ret;
char *order_status_url;
+ json_t *choice_index = json_null();
{
struct TALER_PrivateContractHashP *h_contract = NULL;
@@ -1403,6 +1415,12 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
TALER_amount_is_zero (&gorc->contract_amount));
gorc->last_payment = gorc->timestamp;
}
+ if (-1 != gorc->choice_index) {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Choice index is %d\n",
+ gorc->choice_index);
+ choice_index = json_integer ((json_int_t) gorc->choice_index);
+ }
ret = TALER_MHD_REPLY_JSON_PACK (
gorc->sc.con,
MHD_HTTP_OK,
@@ -1440,7 +1458,11 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
GNUNET_JSON_pack_array_steal ("refund_details",
gorc->refund_details),
GNUNET_JSON_pack_string ("order_status_url",
- order_status_url));
+ order_status_url),
+ {
+ .field_name = "choice_index",
+ .object = choice_index,
+ });
GNUNET_free (order_status_url);
gorc->wire_details = NULL;
gorc->refund_details = NULL;
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 06dbbdf9..12e57a99 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
@@ -20,6 +20,7 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
+#include <gnunet/gnunet_json_lib.h>
#include <taler/taler_json_lib.h>
@@ -84,6 +85,7 @@ 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_object_steal ("description_i18n",
@@ -91,7 +93,10 @@ TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh,
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_string ("kind", kind),
+ // TODO: Implement
+ GNUNET_JSON_pack_int64 ("issued", 0),
+ GNUNET_JSON_pack_int64 ("redeemed", 0)
);
GNUNET_free (details.name);
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index 8bdae3f1..a2e3b493 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -26,6 +26,7 @@
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
@@ -34,6 +35,7 @@
#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include <time.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
@@ -376,14 +378,14 @@ struct OrderContext
unsigned int choices_len;
/**
- * Array of token types referenced in the contract.
+ * Array of token families referenced in the contract.
*/
- struct TALER_MerchantContractTokenAuthority *authorities;
+ struct TALER_MerchantContractTokenFamily *token_families;
/**
- * Length of the @e authorities array.
+ * Length of the @e token_families array.
*/
- unsigned int authorities_len;
+ unsigned int token_families_len;
} parse_choices;
/**
@@ -679,7 +681,6 @@ clean_order (void *cls)
json_decref (oc->merge_inventory.products);
oc->merge_inventory.products = NULL;
}
- // TODO: Check if this is even correct
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
{
GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
@@ -689,6 +690,25 @@ clean_order (void *cls)
oc->parse_choices.choices[i].outputs_len,
0);
}
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ 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++)
+ {
+ GNUNET_CRYPTO_blind_sign_pub_decref(oc->parse_choices.token_families[i].keys[j].pub.public_key);
+ }
+ GNUNET_array_grow (oc->parse_choices.token_families[i].keys,
+ oc->parse_choices.token_families[i].keys_len,
+ 0);
+ }
+ GNUNET_array_grow (oc->parse_choices.token_families,
+ oc->parse_choices.token_families_len,
+ 0);
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
0);
@@ -1324,36 +1344,168 @@ get_exchange_keys (void *cls,
/**
- * Fetch details about the token family with the given @a slug
- * and add them to the list of token authorities. Check if the
- * token family already has a valid key configured and if not,
- * create a new one.
+ * 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.
+ *
+ * @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 tm* timeinfo;
+ time_t seconds;
+
+ seconds = GNUNET_TIME_timestamp_to_s (ts);
+ timeinfo = localtime (&seconds);
+
+ 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;
+ }
+ 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;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
+ {
+ timeinfo->tm_hour = 0;
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
+ {
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+ {
+ timeinfo->tm_sec = 0;
+ }
+ else
+ {
+ return GNUNET_SYSERR;
+ }
+
+ *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))
+ {
+ timeinfo->tm_mday++;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
+ {
+ timeinfo->tm_hour++;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+ {
+ timeinfo->tm_min++;
+ }
+ else
+ {
+ return GNUNET_SYSERR;
+ }
+
+ *end = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
+ return GNUNET_OK;
+}
+
+/**
+ * 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.
*
* @param[in,out] oc order context
* @param slug slug of the token family
- * @param start_date validity start date of the token
+ * @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 MHD_RESULT
-set_token_authority (struct OrderContext *oc,
- const char *slug,
- struct GNUNET_TIME_Timestamp start_date)
+static enum GNUNET_GenericReturnValue
+set_token_family (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp *valid_after)
{
struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
- struct TALER_MerchantContractTokenAuthority authority;
+ struct TALER_MerchantContractTokenFamily *family = NULL;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
- start_date.abs_time,
- // TODO: make this configurable. This is the granularity of token
- // expiration dates.
- GNUNET_TIME_UNIT_DAYS
- );
+ /* 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))
+ {
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "valid_after");
+ return GNUNET_SYSERR;
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.token_families[i].slug,
+ slug))
+ {
+ family = &oc->parse_choices.token_families[i];
+ break;
+ }
+ }
+
+ if (NULL != family)
+ {
+ 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;
+ }
+ }
+ }
qs = TMH_db->lookup_token_family_key (TMH_db->cls,
oc->hc->instance->settings.id,
slug,
- GNUNET_TIME_absolute_to_timestamp (
- min_start_date),
- start_date,
+ min_valid_after,
+ max_valid_after,
&key_details);
if (qs <= 0)
@@ -1386,85 +1538,164 @@ set_token_authority (struct OrderContext *oc,
http_status,
ec,
"token_family_slug");
- return MHD_NO;
+ return GNUNET_SYSERR;
}
- if (NULL == key_details.pub)
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ /* 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))
{
- /* If public key is NULL, private key must also be NULL */
- GNUNET_assert (NULL == key_details.priv);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family expired or not yet valid\n");
+ 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,
+ key_details.token_family.slug);
+ return GNUNET_SYSERR;
+ }
+
+ /* slug is not needed */
+ GNUNET_free (key_details.token_family.slug);
- struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
- struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_TIME_Timestamp valid_before =
- GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (now,
- key_details.token_family.duration));
+ {
+ struct TALER_MerchantContractTokenFamilyKey key;
- GNUNET_CRYPTO_blind_sign_keys_create (&priv,
- &pub,
- // TODO: Make cipher and key length configurable
- GNUNET_CRYPTO_BSA_RSA,
- 4096);
+ if (NULL == family)
+ {
+ 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,
+ };
- struct TALER_TokenFamilyPublicKey token_pub = {
- .public_key = *pub,
- };
- struct TALER_TokenFamilyPrivateKey token_priv = {
- .private_key = *priv,
- };
+ 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;
+ }
- qs = TMH_db->insert_token_family_key (TMH_db->cls,
- slug,
- &token_pub,
- &token_priv,
- GNUNET_TIME_absolute_to_timestamp (now
- ),
- valid_before);
+ GNUNET_array_append (oc->parse_choices.token_families,
+ oc->parse_choices.token_families_len,
+ new_family);
- authority.token_expiration = valid_before;
- authority.pub = &token_pub;
- // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
- }
- else
- {
- authority.token_expiration = key_details.valid_before;
- authority.pub = key_details.pub;
- }
+ family = &oc->parse_choices.token_families[oc->parse_choices.token_families_len - 1];
+ }
- authority.label = slug;
- authority.description = key_details.token_family.description;
- authority.description_i18n = key_details.token_family.description_i18n;
+ if (NULL == key_details.pub.public_key)
+ {
+ /* 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 */
+ GNUNET_assert (NULL == key_details.priv.private_key);
+
+ 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));
+
+ 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;
+ }
- GNUNET_free (key_details.token_family.slug);
- GNUNET_free (key_details.token_family.name);
- if (NULL != key_details.priv)
- {
- GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
- }
+ 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;
+ }
- switch (key_details.token_family.kind)
- {
- case TALER_MERCHANTDB_TFK_Subscription:
- authority.kind = TALER_MCTK_SUBSCRIPTION;
- authority.details.subscription.start_date = key_details.valid_after;
- authority.details.subscription.end_date = key_details.valid_before;
- authority.critical = true;
- // TODO: Set trusted domains
- break;
- case TALER_MERCHANTDB_TFK_Discount:
- authority.kind = TALER_MCTK_DISCOUNT;
- authority.critical = false;
- // TODO: Set expected domains
- break;
- }
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ /* TODO: Make cipher and key length configurable */
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenIssuePublicKeyP token_pub = {
+ .public_key = pub,
+ };
+ struct TALER_TokenIssuePrivateKeyP token_priv = {
+ .private_key = priv,
+ };
- GNUNET_array_append (oc->parse_choices.authorities,
- oc->parse_choices.authorities_len,
- authority);
+ iqs = TMH_db->insert_token_family_key (TMH_db->cls,
+ 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;
- return MHD_YES;
+ switch (iqs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_STORE_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+
+ GNUNET_break (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;
+ } else {
+ key.pub = key_details.pub;
+ key.valid_after = key_details.valid_after;
+ key.valid_before = key_details.valid_before;
+ }
+
+ GNUNET_array_append (family->keys,
+ family->keys_len,
+ key);
+
+ *valid_after = key.valid_after;
+ }
+
+ return GNUNET_OK;
}
@@ -1481,7 +1712,7 @@ serialize_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
json_t *merchant;
- json_t *token_types = json_object ();
+ json_t *token_families = json_object ();
json_t *choices = json_array ();
merchant = GNUNET_JSON_PACK (
@@ -1528,38 +1759,54 @@ serialize_order (struct OrderContext *oc)
}
}
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
{
- struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.
- authorities[i];
+ json_t *keys = json_array();
+ struct TALER_MerchantContractTokenFamily *family = &oc->parse_choices.token_families[i];
+
+ for (unsigned int j = 0; j<family->keys_len; j++)
+ {
+ struct TALER_MerchantContractTokenFamilyKey key = family->keys[j];
+
+ json_t *jkey = GNUNET_JSON_PACK (
+ /* 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),
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ key.valid_after),
+ GNUNET_JSON_pack_timestamp ("valid_before",
+ key.valid_before)
+ );
- // TODO: Finish spec to clearly define how token families are stored in
- // ContractTerms.
- // Here are some thoughts:
- // - Multiple keys of the same token family can be referenced in
- // one contract. E.g. exchanging old subscription for new.
- // - TokenAuthority should be renamed to TokenFamily for consistency.
- // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but
- // every token-based in- or output needs to have a valid_after date,
- // so it's clear with key is referenced.
- json_t *jauthority = GNUNET_JSON_PACK (
+ GNUNET_assert (0 == json_array_append_new (keys, jkey));
+ }
+
+ /* TODO: Add 'details' field. */
+ json_t *jfamily = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ family->name),
GNUNET_JSON_pack_string ("description",
- authority->description),
+ family->description),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
- authority->description_i18n)),
- GNUNET_JSON_pack_data_auto ("h_pub",
- &authority->pub->public_key.pub_key_hash),
- GNUNET_JSON_pack_data_auto ("pub",
- &authority->pub->public_key.pub_key_hash),
- GNUNET_JSON_pack_timestamp ("token_expiration",
- authority->token_expiration)
- );
+ family->description_i18n)),
+ GNUNET_JSON_pack_array_steal ("keys",
+ keys),
+ GNUNET_JSON_pack_bool ("critical",
+ family->critical)
+ );
- GNUNET_assert (0 ==
- json_object_set_new (token_types,
- authority->label,
- jauthority));
+ GNUNET_assert (0 == json_object_set_new (token_families,
+ family->slug,
+ jfamily));
}
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
@@ -1573,25 +1820,19 @@ serialize_order (struct OrderContext *oc)
{
struct TALER_MerchantContractInput *input = &choice->inputs[j];
- json_t *jinput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- input->type)
- );
+ /* For now, only tokens are supported */
+ GNUNET_assert (TALER_MCIT_TOKEN == input->type);
- if (TALER_MCIT_TOKEN == input->type)
- {
- GNUNET_assert (0 ==
- json_object_set_new (jinput,
- "number",
- json_integer (
- input->details.token.count)));
- GNUNET_assert (0 ==
- json_object_set_new (jinput,
- "token_family_slug",
- json_string (
- input->details.token.
- token_family_slug)));
- }
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("kind",
+ TMH_string_from_contract_input_type (input->type)),
+ 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)
+ );
GNUNET_assert (0 == json_array_append_new (inputs, jinput));
}
@@ -1600,26 +1841,19 @@ serialize_order (struct OrderContext *oc)
{
struct TALER_MerchantContractOutput *output = &choice->outputs[j];
- json_t *joutput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- output->type)
- );
-
- if (TALER_MCOT_TOKEN == output->type)
- {
- GNUNET_assert (0 ==
- json_object_set_new (joutput,
- "number",
- json_integer (
- output->details.token.count)));
+ /* For now, only tokens are supported */
+ GNUNET_assert (TALER_MCOT_TOKEN == output->type);
- GNUNET_assert (0 ==
- json_object_set_new (joutput,
- "token_family_slug",
- json_string (
- output->details.token.
- token_family_slug)));
- }
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("kind",
+ TMH_string_from_contract_output_type (output->type)),
+ GNUNET_JSON_pack_string ("token_family_slug",
+ 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_assert (0 == json_array_append (outputs, joutput));
}
@@ -1691,13 +1925,13 @@ serialize_order (struct OrderContext *oc)
TALER_JSON_pack_amount ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_incref ("choices",
- choices)
- ),
+ GNUNET_JSON_pack_array_steal ("choices",
+ choices)
+ ),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("token_types",
- token_types)
- ),
+ GNUNET_JSON_pack_object_steal ("token_families",
+ token_families)
+ ),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
@@ -1780,8 +2014,8 @@ set_max_fee (struct OrderContext *oc)
static bool
set_exchanges (struct OrderContext *oc)
{
- /* Note: re-building 'oc->exchanges' every time here might be a tad
- expensive; could likely consider caching the result if it starts to
+ /* Note: re-building 'oc->set_exchanges.exchanges' every time here might be a
+ tad expensive; could likely consider caching the result if it starts to
matter. */
if (NULL == oc->set_exchanges.exchanges)
{
@@ -1852,6 +2086,8 @@ 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. */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("version",
@@ -2314,10 +2550,14 @@ parse_choices (struct OrderContext *oc)
const json_t *jinputs;
const json_t *joutputs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("inputs",
- &jinputs),
- GNUNET_JSON_spec_array_const ("outputs",
- &joutputs),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
@@ -2339,14 +2579,15 @@ parse_choices (struct OrderContext *oc)
return;
}
- if (! json_is_array (jinputs) ||
- ! json_is_array (joutputs))
+ if (0 == json_array_size(jinputs) && 0 == json_array_size(joutputs))
{
- GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice #%u must have at least one input or output\n",
+ i);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inputs or outputs");
+ "choice");
return;
}
@@ -2356,10 +2597,6 @@ parse_choices (struct OrderContext *oc)
size_t idx;
json_array_foreach ((json_t *) jinputs, idx, jinput)
{
- // TODO: Assuming this struct would have more fields, would i use GNUNET_new then?
- // Or should i use GNUNET_grow first and then get the element using the index?
- // Assuming you add a field in the future, it's easier to that way, so you don't
- // free it.
struct TALER_MerchantContractInput input = {.details.token.count = 1};
const char *kind;
const char *ierror_name;
@@ -2370,6 +2607,8 @@ parse_choices (struct OrderContext *oc)
&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 (
@@ -2396,7 +2635,7 @@ parse_choices (struct OrderContext *oc)
return;
}
- input.type = TMH_string_to_contract_input_type (kind);
+ input.type = TMH_contract_input_type_from_string (kind);
if (TALER_MCIT_INVALID == input.type)
{
@@ -2416,29 +2655,12 @@ parse_choices (struct OrderContext *oc)
continue;
}
- bool found = false;
-
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ if (GNUNET_OK != set_token_family (oc,
+ input.details.token.token_family_slug,
+ &input.details.token.valid_after))
{
- if (0 == strcmp (oc->parse_choices.authorities[i].label,
- input.details.token.token_family_slug))
- {
- found = true;
- break;
- }
- }
-
- if (! found)
- {
- MHD_RESULT res;
- res = set_token_authority (oc,
- input.details.token.token_family_slug,
- input.details.token.valid_after);
-
- if (MHD_NO == res)
- {
- return;
- }
+ /* error is already scheduled, return. */
+ return;
}
GNUNET_array_append (oc->parse_choices.choices[i].inputs,
@@ -2463,6 +2685,7 @@ parse_choices (struct OrderContext *oc)
&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 (
@@ -2491,7 +2714,7 @@ parse_choices (struct OrderContext *oc)
return;
}
- output.type = TMH_string_to_contract_output_type (kind);
+ output.type = TMH_contract_output_type_from_string (kind);
if (TALER_MCOT_INVALID == output.type)
{
@@ -2509,33 +2732,16 @@ parse_choices (struct OrderContext *oc)
if (0 == output.details.token.count)
{
- /* Ignore outputs with 'number' field set to 0 */
+ /* Ignore outputs with 'number' field set to 0. */
continue;
}
- bool found = false;
-
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ if (GNUNET_OK != set_token_family (oc,
+ output.details.token.token_family_slug,
+ &output.details.token.valid_after))
{
- if (0 == strcmp (oc->parse_choices.authorities[i].label,
- output.details.token.token_family_slug))
- {
- found = true;
- break;
- }
- }
-
- if (! found)
- {
- MHD_RESULT res;
- res = set_token_authority (oc,
- output.details.token.token_family_slug,
- output.details.token.valid_after);
-
- if (MHD_NO == res)
- {
- return;
- }
+ /* Error is already scheduled, return. */
+ return;
}
GNUNET_array_append (oc->parse_choices.choices[i].outputs,
@@ -2651,7 +2857,6 @@ merge_inventory (struct OrderContext *oc)
/* case listed to make compilers happy */
GNUNET_assert (0);
}
- json_decref (oc->merge_inventory.products);
reply_with_error (oc,
http_status,
ec,
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 f4472c39..5a342af2 100644
--- a/src/backend/taler-merchant-httpd_private-post-token-families.c
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -25,6 +25,7 @@
#include "platform.h"
#include "taler-merchant-httpd_private-post-token-families.h"
#include "taler-merchant-httpd_helper.h"
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_json_lib.h>
@@ -74,6 +75,7 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
const char *kind = NULL;
+ bool no_valid_after = false;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("slug",
@@ -87,8 +89,10 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
&details.description_i18n),
NULL),
GNUNET_JSON_spec_string ("kind", &kind),
- GNUNET_JSON_spec_timestamp ("valid_after",
- &details.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &details.valid_after),
+ &no_valid_after),
GNUNET_JSON_spec_timestamp ("valid_before",
&details.valid_before),
GNUNET_JSON_spec_relative_time ("duration",
@@ -112,6 +116,33 @@ TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
}
}
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ if (no_valid_after) {
+ details.valid_after = now;
+ }
+
+ /* Ensure that valid_after is before valid_before */
+ if (GNUNET_TIME_timestamp_cmp (details.valid_after, >=, details.valid_before))
+ {
+ 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_before");
+ }
+
+ /* Ensure that valid_after is not in the past */
+ if (GNUNET_TIME_timestamp_cmp (details.valid_after, <, now))
+ {
+ 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");
+ }
if (strcmp (kind, "discount") == 0)
details.kind = TALER_MERCHANTDB_TFK_Discount;