diff options
-rw-r--r-- | src/backend/Makefile.am | 1 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_contract.c | 47 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_contract.h | 592 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 720 | ||||
-rw-r--r-- | src/backenddb/Makefile.am | 2 | ||||
-rw-r--r-- | src/backenddb/pg_insert_token_family.h | 2 | ||||
-rw-r--r-- | src/backenddb/pg_insert_token_family_key.c | 95 | ||||
-rw-r--r-- | src/backenddb/pg_insert_token_family_key.h | 46 | ||||
-rw-r--r-- | src/backenddb/pg_lookup_token_family.c | 4 | ||||
-rw-r--r-- | src/backenddb/pg_lookup_token_family_key.c | 161 | ||||
-rw-r--r-- | src/backenddb/pg_lookup_token_family_key.h | 50 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 7 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 59 |
13 files changed, 1728 insertions, 58 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index d521608d..dbc7cde8 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -25,6 +25,7 @@ bin_PROGRAMS = \ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd.c taler-merchant-httpd.h \ taler-merchant-httpd_config.c taler-merchant-httpd_config.h \ + taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \ taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \ taler-merchant-httpd_get-orders-ID.c \ taler-merchant-httpd_get-orders-ID.h \ diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c new file mode 100644 index 00000000..38c82e70 --- /dev/null +++ b/src/backend/taler-merchant-httpd_contract.c @@ -0,0 +1,47 @@ +/* + This file is part of TALER + (C) 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 + 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 taler-merchant-httpd_contract.c + * @brief shared logic for contract terms handling + * @author Christian Blättler + */ +#include "platform.h" +#include <jansson.h> +#include "taler-merchant-httpd_contract.h" + +enum TALER_MerchantContractInputType +TMH_string_to_contract_input_type (const char *str) +{ + /* For now, only 'token' is the only supported option. */ + if (0 == strcmp("token", str)) + { + return TALER_MCIT_TOKEN; + } + + return TALER_MCIT_INVALID; +} + +enum TALER_MerchantContractOutputType +TMH_string_to_contract_output_type (const char *str) +{ + /* For now, only 'token' is the only supported option. */ + if (0 == strcmp("token", str)) + { + return TALER_MCOT_TOKEN; + } + + return TALER_MCOT_INVALID; +} diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h new file mode 100644 index 00000000..defe6479 --- /dev/null +++ b/src/backend/taler-merchant-httpd_contract.h @@ -0,0 +1,592 @@ +/* + This file is part of TALER + (C) 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 + 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 taler-merchant-httpd_contract.h + * @brief shared logic for contract terms handling + * @author Christian Blättler + */ +#include "taler-merchant-httpd.h" +#include <gnunet/gnunet_time_lib.h> +#include <jansson.h> + + +/** + * Possible versions of the contract terms. + */ +enum TALER_MerchantContractVersion +{ + + /** + * Version 0 + */ + TALER_MCV_V0 = 0, + + /** + * Version 1 + */ + TALER_MCV_V1 = 1 +}; + +/** + * Possible token kinds. + */ +enum TALER_MerchantContractTokenKind +{ + + /** + * Subscription token kind + */ + TALER_MCTK_SUBSCRIPTION = 0, + + /** + * Discount token kind + */ + TALER_MCTK_DISCOUNT = 1 +}; + +/** + * Possible input types for the contract terms. + */ +enum TALER_MerchantContractInputType +{ + + /** + * Input type invalid + */ + TALER_MCIT_INVALID = 0, + + /** + * Input type coin + */ + TALER_MCIT_COIN = 1, + + /** + * Input type token + */ + TALER_MCIT_TOKEN = 2 +}; + +/** + * Contract input (part of the v1 contract terms). + */ +struct TALER_MerchantContractInput +{ + /** + * Type of the input. + */ + enum TALER_MerchantContractInputType type; + + union + { + /** + * Coin-based input (ration). (Future work, only here for reference) + */ + // struct + // { + // /** + // * Price to be paid. + // */ + // struct TALER_Amount price; + + // /** + // * Base URL of the ration authority. + // */ + // const char *ration_authority_url; + // } coin; + + /** + * Token-based input. + */ + struct + { + /** + * 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. + */ + unsigned int count; + } token; + } details; +}; + +/** + * Possible output types for the contract terms. + */ +enum TALER_MerchantContractOutputType +{ + + /** + * Invalid output type + */ + TALER_MCOT_INVALID = 0, + + /** + * Output type coin + */ + TALER_MCOT_COIN = 1, + + /** + * Output type token + */ + TALER_MCOT_TOKEN = 2, + + /** + * Output type tax-receipt + */ + TALER_MCOT_TAX_RECEIPT = 3 + +}; + +/** + * Contract output (part of the v1 contract terms). + */ +struct TALER_MerchantContractOutput +{ + /** + * Type of the output. + */ + enum TALER_MerchantContractOutputType type; + + union + { + /** + * Coin-based output. + */ + struct { + /** + * 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. + */ + const char *exchange_url; + } coin; + + /** + * Tax-receipt output. + */ + struct + { + /** + * Base URL of the donation authority that will issue the tax receipt. + */ + const char *donau_url; + } tax_receipt; + + /** + * Token-based output. + */ + struct + { + /** + * 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; + + /** + * Number of tokens of this type required. Defaults to one if the + * field is not provided. + */ + unsigned int count; + } token; + } details; +}; + +/** + * Contract choice (part of the v1 contract terms). + */ +struct TALER_MerchantContractChoice +{ + /** + * List of inputs the wallet must provision (all of them) to satisfy the + * conditions for the contract. + */ + struct TALER_MerchantContractInput *inputs; + + /** + * Length of the @e inputs array. + */ + unsigned int inputs_len; + + /** + * List of outputs the merchant promises to yield (all of them) once + * the contract is paid. + */ + struct TALER_MerchantContractOutput *outputs; + + /** + * Length of the @e outputs array. + */ + unsigned int outputs_len; +}; + +struct TALER_MerchantContractLimits +{ + /** + * Currency these limits are for. + */ + char currency[TALER_CURRENCY_LEN]; + + /** + * The hash of the merchant instance's wire details. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * 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. + */ + char *wire_method; + + /** + * Maximum total deposit fee accepted by the merchant for this contract. + */ + struct TALER_Amount max_fee; +}; + +struct TALER_MerchantContractTokenAuthority +{ + /** + * Label of the token authority. + */ + const char *label; + + /** + * Human-readable description of the semantics of the tokens issued by + * this authority. + */ + char *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; + + /** + * When will tokens signed by this key expire? + */ + struct GNUNET_TIME_Timestamp token_expiration; + + /** + * Must a wallet understand this token type to process contracts that + * consume or yield it? + */ + bool critical; + + /** + * Kind of the token. + */ + enum TALER_MerchantContractTokenKind kind; + + /** + * Kind-specific information about the token. + */ + union + { + /** + * 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. + */ + const char **trusted_domains; + + /** + * Length of the @e trusted_domains array. + */ + unsigned int trusted_domains_len; + } subscription; + + /** + * Discount token. + */ + 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). + */ + const char **expected_domains; + + /** + * Length of the @e expected_domains array. + */ + unsigned int expected_domains_len; + + } discount; + } details; +}; + +/** + * Struct to hold contract terms in v0 and v1 format. v0 contracts are mdoelled + * as a v1 contract with a single choice and no inputs and outputs. Use the + * version field to explicitly differentiate between v0 and v1 contracts. + */ +struct TALER_MerchantContract +{ + /** + * URL where the same contract could be ordered again (if available). + */ + const char *public_reorder_url; + + /** + * Our order ID. + */ + const char *order_id; + + /** + * Merchant base URL. + */ + char *merchant_base_url; + + /** + * Merchant information. + */ + struct + { + /** + * Legal name of the instance + */ + char *name; + + /** + * Merchant's site url + */ + char *website; + + /** + * Email contact for customers + */ + char *email; + + /** + * merchant's logo data uri + */ + char *logo; + + /** + * Merchant address + */ + json_t *address; + + /** + * Jurisdiction of the business + */ + json_t *jurisdiction; + } merchant; + + /** + * Price to be paid for the transaction. Could be 0. The price is in addition + * to other instruments, such as rations and tokens. + * The exchange will subtract deposit fees from that amount + * before transferring it to the merchant. + */ + struct TALER_Amount brutto; + + /** + * Summary of the contract. + */ + const char *summary; + + /** + * Internationalized summary. + */ + json_t *summary_i18n; + + /** + * URL that will show that the contract was successful + * after it has been paid for. + */ + const char *fulfillment_url; + + /** + * Message shown to the customer after paying for the contract. + * Either fulfillment_url or fulfillment_message must be specified. + */ + const char *fulfillment_message; + + /** + * Map from IETF BCP 47 language tags to localized fulfillment messages. + */ + json_t *fulfillment_message_i18n; + + /** + * Array of products that are part of the purchase. + */ + const json_t *products; + + /** + * Timestamp of the contract. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Deadline for refunds. + */ + struct GNUNET_TIME_Timestamp refund_deadline; + + /** + * Specifies for how long the wallet should try to get an + * automatic refund for the purchase. + */ + struct GNUNET_TIME_Relative auto_refund; + + /** + * Payment deadline. + */ + struct GNUNET_TIME_Timestamp pay_deadline; + + /** + * Wire transfer deadline. + */ + struct GNUNET_TIME_Timestamp wire_deadline; + + /** + * Delivery date. + */ + struct GNUNET_TIME_Timestamp delivery_date; + + /** + * Delivery location. + */ + json_t *delivery_location; + + /** + * Nonce generated by the wallet and echoed by the merchant + * in this field when the proposal is generated. + */ + const char *nonce; + + /** + * Extra data that is only interpreted by the merchant frontend. + */ + const json_t *extra; + + /** + * Specified version of the contract. + */ + enum TALER_MerchantContractVersion version; + + /** + * Array of possible specific contracts the wallet/customer may choose + * from by selecting the respective index when signing the deposit + * confirmation. + */ + struct TALER_MerchantContractChoice *choices; + + /** + * Length of the @e choices array. + */ + unsigned int choices_len; + + /** + * Array of token authorities. + */ + struct TALER_MerchantContractTokenAuthority *token_authorities; + + /** + * Length of the @e token_authorities array. + */ + unsigned int token_authorities_len; + + /** + * Maximum fee as given by the client request. + */ + struct TALER_Amount max_fee; + + // TODO: Add exchanges array +}; + +enum TALER_MerchantContractInputType +TMH_string_to_contract_input_type (const char *str); + +enum TALER_MerchantContractOutputType +TMH_string_to_contract_output_type (const char *str); + +/** + * Serialize @a contract to a JSON object, ready to be stored in the database. + * The @a contract can be of v0 or v1. + * + * @param[in] contract contract struct to serialize + * @param[in] instance merchant instance for this contract + * @param[in] exchanges JSON array of exchanges + * @param[out] out serialized contract as JSON object + * @return #GNUNET_OK on success + * #GNUNET_NO if @a contract was not valid + * #GNUNET_SYSERR on failure + */ +enum GNUNET_GenericReturnValue +TMH_serialize_contract (const struct TALER_MerchantContract *contract, + const struct TMH_MerchantInstance *instance, + json_t *exchanges, + json_t **out); + +enum GNUNET_GenericReturnValue +TMH_serialize_contract_v0 (const struct TALER_MerchantContract *contract, + const struct TMH_MerchantInstance *instance, + json_t *exchanges, + json_t **out); + +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 diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 7ca56319..b2070bcc 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -22,19 +22,25 @@ * @brief the POST /orders handler * @author Christian Grothoff * @author Marcello Stanisci + * @author Christian Blättler */ #include "platform.h" #include <gnunet/gnunet_common.h> #include <gnunet/gnunet_json_lib.h> #include <gnunet/gnunet_time_lib.h> #include <jansson.h> +#include <microhttpd.h> #include <string.h> +#include <taler/taler_error_codes.h> #include <taler/taler_signatures.h> #include <taler/taler_json_lib.h> +#include "taler-merchant-httpd.h" #include "taler-merchant-httpd_private-post-orders.h" #include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_private-get-orders.h" +#include "taler_merchantdb_plugin.h" /** @@ -231,43 +237,58 @@ struct OrderContext struct { /** + * Version of the contract terms. + */ + enum TALER_MerchantContractVersion version; + + /** * Our order ID. */ const char *order_id; /** - * Summary of the order. - */ + * Summary of the contract. + */ const char *summary; /** - * Internationalized summary. - */ + * Internationalized summary. + */ json_t *summary_i18n; /** - * URL where the same contract could be ordered again (if available). - */ - const char *public_reorder_url; - - /** - * URL that will show that the order 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; /** - * Message shown to the customer after paying for the order. - * Either fulfillment_url or fulfillment_message must be specified. - */ + * Message shown to the customer after paying for the contract. + * Either fulfillment_url or fulfillment_message must be specified. + */ const char *fulfillment_message; /** - * Map from IETF BCP 47 language tags to localized fulfillment messages. - */ + * Map from IETF BCP 47 language tags to localized fulfillment messages. + */ json_t *fulfillment_message_i18n; /** + * Array of products that are part of the purchase. + */ + const json_t *products; + + /** + * URL where the same contract could be ordered again (if available). + */ + const char *public_reorder_url; + + /** + * Array of contract choices. Is null for v0 contracts. + */ + const json_t *choices; + + /** * Merchant base URL. */ char *merchant_base_url; @@ -303,14 +324,9 @@ struct OrderContext json_t *delivery_location; /** - * Array of products that are part of the purchase. - */ - const json_t *products; - - /** - * Gross amount value of the contract. Used to - * compute @e max_stefan_fee. - */ + * Gross amount value of the contract. Used to + * compute @e max_stefan_fee. + */ struct TALER_Amount brutto; /** @@ -343,6 +359,34 @@ struct OrderContext } parse_order; /** + * Information set in the ORDER_PHASE_PARSE_CHOICES phase. + */ + struct + { + /** + * Array of possible specific contracts the wallet/customer may choose + * from by selecting the respective index when signing the deposit + * confirmation. + */ + struct TALER_MerchantContractChoice *choices; + + /** + * Length of the @e choices array. + */ + unsigned int choices_len; + + /** + * Array of token types referenced in the contract. + */ + struct TALER_MerchantContractTokenAuthority *authorities; + + /** + * Length of the @e authorities array. + */ + unsigned int authorities_len; + } parse_choices; + + /** * Information set in the ORDER_PHASE_MERGE_INVENTORY phase. */ struct @@ -477,6 +521,7 @@ struct OrderContext { ORDER_PHASE_PARSE_REQUEST, ORDER_PHASE_PARSE_ORDER, + ORDER_PHASE_PARSE_CHOICES, ORDER_PHASE_MERGE_INVENTORY, ORDER_PHASE_ADD_PAYMENT_DETAILS, ORDER_PHASE_SET_EXCHANGES, @@ -634,6 +679,16 @@ 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, + oc->parse_choices.choices[i].inputs_len, + 0); + GNUNET_array_grow (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + 0); + } GNUNET_array_grow (oc->parse_request.inventory_products, oc->parse_request.inventory_products_length, 0); @@ -1262,6 +1317,145 @@ get_exchange_keys (void *cls, rx); } +/** + * 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. + * + * @param[in,out] oc order context + * @param slug slug of the token family + * @param start_date validity start date of the token + */ +static MHD_RESULT +set_token_authority (struct OrderContext *oc, + const char *slug, + struct GNUNET_TIME_Timestamp start_date) +{ + struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details; + struct TALER_MerchantContractTokenAuthority authority; + 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 + ); + + 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, + &key_details); + + if (qs <= 0) + { + enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + unsigned int http_status = 0; + + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_FETCH_FAILED; + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + 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 unknown\n"); + http_status = MHD_HTTP_NOT_FOUND; + /* TODO: Add specific error code */ + ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* case listed to make compilers happy */ + GNUNET_assert (0); + } + GNUNET_break (0); + reply_with_error (oc, + http_status, + ec, + "authority_label"); + return MHD_NO; + } + + if (NULL == key_details.pub) + { + /* If public key is NULL, private key must also be NULL */ + GNUNET_assert (NULL == key_details.priv); + + 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)); + + // TODO: Should I add a wrapper around this, so it directly accepts structs of type + // TALER_TokenFamilyPublicKey and TALER_TokenFamilyPrivateKey? + GNUNET_CRYPTO_blind_sign_keys_create (&priv, + &pub, + // TODO: Make cipher and key length configurable + GNUNET_CRYPTO_BSA_RSA, + 4096); + + struct TALER_TokenFamilyPublicKey token_pub = { + .public_key = *pub, + }; + struct TALER_TokenFamilyPrivateKey token_priv = { + .private_key = *priv, + }; + + qs = TMH_db->insert_token_family_key (TMH_db->cls, + slug, + &token_pub, + &token_priv, + GNUNET_TIME_absolute_to_timestamp (now), + valid_before); + + 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; + } + + authority.label = slug; + authority.description = key_details.token_family.description; + authority.description_i18n = key_details.token_family.description_i18n; + + 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); + } + + 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_array_append (oc->parse_choices.authorities, + oc->parse_choices.authorities_len, + authority); + + return MHD_YES; +} /** * Serialize order into @a oc->serialize_order.contract, @@ -1276,45 +1470,159 @@ 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 *choices = json_array (); merchant = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("name", - settings->name), + settings->name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("website", - settings->website)), + settings->website)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("email", - settings->email)), + settings->email)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("logo", - settings->logo))); + settings->logo))); GNUNET_assert (NULL != merchant); { - json_t *loca = settings->address; + json_t *loca; + /* Handle merchant address */ + loca = settings->address; if (NULL != loca) { + loca = json_deep_copy (loca); + GNUNET_assert (NULL != loca); GNUNET_assert (0 == - json_object_set (merchant, - "address", - loca)); + json_object_set_new (merchant, + "address", + loca)); } } { - json_t *juri = settings->jurisdiction; + json_t *juri; /* Handle merchant jurisdiction */ + juri = settings->jurisdiction; if (NULL != juri) { + juri = json_deep_copy (juri); + GNUNET_assert (NULL != juri); GNUNET_assert (0 == - json_object_set (merchant, - "jurisdiction", - juri)); + json_object_set_new (merchant, + "jurisdiction", + juri)); } } + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) + { + struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; + + // TODO: Finish spec to clearly define how token families are stored in + // ContractTerms. + // Here are some thoughts: + // - Multiple keys of the same token family can be referrenced in + // one contract. E.g. exchanging old subscription for new. + // - TokenAuthority should be renamed to TokenFamily for consistency. + // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but + // every token-based in- or ouput needs to have a valid_after date, + // so it's clear with key is referenced. + json_t *jauthority = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + authority->description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + authority->description_i18n)), + GNUNET_JSON_pack_data_auto ("h_pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_data_auto ("pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_timestamp ("token_expiration", + authority->token_expiration) + ); + + GNUNET_assert (0 == + json_object_set_new (token_types, + authority->label, + jauthority)); + } + + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; + + json_t *inputs = json_array (); + json_t *outputs = json_array (); + + for (unsigned int j = 0; j<choice->inputs_len; j++) + { + struct TALER_MerchantContractInput *input = &choice->inputs[j]; + + json_t *jinput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + input->type) + ); + + if (TALER_MCIT_TOKEN == input->type) + { + GNUNET_assert(0 == + json_object_set_new(jinput, + "number", + json_integer ( + input->details.token.count))); + GNUNET_assert(0 == + json_object_set_new(jinput, + "token_family_slug", + json_string ( + input->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append_new (inputs, jinput)); + } + + for (unsigned int j = 0; j<choice->outputs_len; j++) + { + 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))); + + GNUNET_assert(0 == + json_object_set_new(joutput, + "token_family_slug", + json_string ( + output->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append (outputs, joutput)); + } + + json_t *jchoice = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); + + GNUNET_assert (0 == json_array_append (choices, jchoice)); + } + oc->serialize_order.contract = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("version", + oc->parse_order.version), GNUNET_JSON_pack_string ("summary", oc->parse_order.summary), GNUNET_JSON_pack_allow_null ( @@ -1368,6 +1676,14 @@ 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_allow_null ( + GNUNET_JSON_pack_object_incref ("token_types", + token_types) + ), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("extra", (json_t *) oc->parse_order.extra)) ); @@ -1378,6 +1694,7 @@ serialize_order (struct OrderContext *oc) "refund_deadline", GNUNET_JSON_from_timestamp ( oc->parse_order.refund_deadline))); + GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "Refund deadline for contact is %llu\n", @@ -1401,7 +1718,6 @@ serialize_order (struct OrderContext *oc) oc->phase++; } - /** * Set max_fee in @a oc based on STEFAN value if * not yet present. Upon success, continue @@ -1437,7 +1753,6 @@ set_max_fee (struct OrderContext *oc) oc->phase++; } - /** * Set list of acceptable exchanges in @a oc. Upon success, continue * processing with set_max_fee(). @@ -1503,8 +1818,8 @@ set_exchanges (struct OrderContext *oc) /** - * Add missing fields to the order. Upon success, continue - * processing with merge_inventory(). + * Parse the order field of the request. Upon success, continue + * processing with parse_choices(). * * @param[in,out] oc order context */ @@ -1514,12 +1829,17 @@ parse_order (struct OrderContext *oc) const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; const char *merchant_base_url = NULL; + const char *version = NULL; const json_t *jmerchant = NULL; /* auto_refund only needs to be type-checked, * mostly because in GNUnet relative times can't * be negative. */ bool no_fee; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("version", + &version), + NULL), TALER_JSON_spec_amount_any ("amount", &oc->parse_order.brutto), GNUNET_JSON_spec_string ("summary", @@ -1537,10 +1857,6 @@ parse_order (struct OrderContext *oc) &oc->parse_order.order_id), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("public_reorder_url", - &oc->parse_order.public_reorder_url), - NULL), - GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("fulfillment_message", &oc->parse_order.fulfillment_message), NULL), @@ -1553,6 +1869,14 @@ parse_order (struct OrderContext *oc) &oc->parse_order.fulfillment_url), NULL), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("public_reorder_url", + &oc->parse_order.public_reorder_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("choices", + &oc->parse_order.choices), + NULL), + GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_web_url ("merchant_base_url", &merchant_base_url), NULL), @@ -1616,6 +1940,46 @@ parse_order (struct OrderContext *oc) ret); return; } + if (NULL == version || 0 == strcmp("0", version)) + { + oc->parse_order.version = TALER_MCV_V0; + + if (NULL != oc->parse_order.choices) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR, + "choices array must be null for v0 contracts"); + return; + } + } + else if (0 == strcmp("1", version)) + { + oc->parse_order.version = TALER_MCV_V1; + + if (! json_is_array(oc->parse_order.choices)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "order.choices is not a valid array"); + return; + } + } + else + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_VERSION_MALFORMED, + "invalid version specified in order, supported are null, '0' or '1'"); + return; + } if (! TMH_test_exchange_configured_for_currency ( oc->parse_order.brutto.currency)) { @@ -1628,9 +1992,9 @@ parse_order (struct OrderContext *oc) return; } if ( (! no_fee) && - (GNUNET_OK != - TALER_amount_cmp_currency (&oc->parse_order.brutto, - &oc->parse_order.max_fee)) ) + (GNUNET_OK != + TALER_amount_cmp_currency (&oc->parse_order.brutto, + &oc->parse_order.max_fee)) ) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -1907,6 +2271,263 @@ parse_order (struct OrderContext *oc) oc->phase++; } +/** + * Parse contract choices. Upon success, continue + * processing with merge_inventory(). + * + * @param[in,out] oc order context + */ +static void +parse_choices (struct OrderContext *oc) +{ + if (NULL == oc->parse_order.choices) + { + oc->phase++; + return; + } + + GNUNET_array_grow (oc->parse_choices.choices, + oc->parse_choices.choices_len, + json_array_size (oc->parse_order.choices)); + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + const char *error_name; + unsigned int error_line; + 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 () + }; + enum GNUNET_GenericReturnValue ret; + + ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i), + spec, + &error_name, + &error_line); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Choice parsing failed: %s:%u\n", + error_name, + error_line); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "choice"); + return; + } + + if (! json_is_array (jinputs) || + ! json_is_array (joutputs)) + { + GNUNET_break_op (0); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inputs or outputs"); + return; + } + + { + // TODO: Maybe move to a separate function + const json_t *jinput; + 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; + 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), + 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() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jinput, + ispec, + &ierror_name, + &ierror_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u for field %s\n", + (unsigned int) idx, + ierror_name); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + ierror_name); + return; + } + + input.type = TMH_string_to_contract_input_type (kind); + + if (TALER_MCIT_INVALID == input.type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'kind' invalid in input #%u\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "kind"); + return; + } + + if (0 == input.details.token.count) + { + /* Ignore inputs with 'number' field set to 0 */ + continue; + } + + bool found = false; + + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) + { + 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; + } + } + + GNUNET_array_append (oc->parse_choices.choices[i].inputs, + oc->parse_choices.choices[i].inputs_len, + input); + } + } + + { + const json_t *joutput; + size_t idx; + json_array_foreach ((json_t *) joutputs, idx, joutput) + { + struct TALER_MerchantContractOutput output = { .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", + &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() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (joutput, + ispec, + &ierror_name, + &ierror_line)) + { + GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (ispec); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u for field %s\n", + (unsigned int) idx, + ierror_name); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + ierror_name); + return; + } + + output.type = TMH_string_to_contract_output_type (kind); + + if (TALER_MCOT_INVALID == output.type) + { + GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (ispec); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'kind' invalid in output #%u\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "kind"); + return; + } + + if (0 == output.details.token.count) + { + /* 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 (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; + } + } + + GNUNET_array_append (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + output); + } + } + } + + oc->phase++; +} /** * Process the @a payment_target and add the details of how the @@ -2305,6 +2926,9 @@ TMH_private_post_orders ( case ORDER_PHASE_PARSE_ORDER: parse_order (oc); break; + case ORDER_PHASE_PARSE_CHOICES: + parse_choices (oc); + break; case ORDER_PHASE_MERGE_INVENTORY: merge_inventory (oc); break; diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index e7fb15cf..defc3cf9 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -173,6 +173,8 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_lookup_token_families.h pg_lookup_token_families.c \ pg_delete_token_family.h pg_delete_token_family.c \ pg_update_token_family.h pg_update_token_family.c \ + pg_insert_token_family_key.h pg_insert_token_family_key.c \ + pg_lookup_token_family_key.h pg_lookup_token_family_key.c \ plugin_merchantdb_postgres.c \ pg_helper.h pg_helper.c libtaler_plugin_merchantdb_postgres_la_LIBADD = \ diff --git a/src/backenddb/pg_insert_token_family.h b/src/backenddb/pg_insert_token_family.h index e05755a6..d584f5e7 100644 --- a/src/backenddb/pg_insert_token_family.h +++ b/src/backenddb/pg_insert_token_family.h @@ -28,7 +28,7 @@ /** * @param cls closure - * @param instance_id instance to insert token family for TODO: Is this needed? + * @param instance_id instance to insert token family for * @param token_family_slug slug of the token family to insert * @param details the token family details to insert * @return database result code diff --git a/src/backenddb/pg_insert_token_family_key.c b/src/backenddb/pg_insert_token_family_key.c new file mode 100644 index 00000000..30525d5f --- /dev/null +++ b/src/backenddb/pg_insert_token_family_key.c @@ -0,0 +1,95 @@ +/* + 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_insert_token_family_key.c + * @brief Implementation of the insert_token_family_key function for Postgres + * @author Christian Blättler + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_pq_lib.h> +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_insert_token_family_key.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_insert_token_family_key (void *cls, + const char *token_family_slug, + const struct TALER_TokenFamilyPublicKey *pub, + const struct TALER_TokenFamilyPrivateKey *priv, + const struct GNUNET_TIME_Timestamp valid_after, + const struct GNUNET_TIME_Timestamp valid_before) +{ + struct PostgresClosure *pg = cls; + const char *cipher; + // struct GNUNET_HashCode pub_hash; + + switch (pub->public_key.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + cipher = "rsa"; + break; + case GNUNET_CRYPTO_BSA_CS: + cipher = "cs"; + break; + case GNUNET_CRYPTO_BSA_INVALID: + /* case listed to make compilers happy */ + GNUNET_assert (0); + } + + 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 (&valid_after), + GNUNET_PQ_query_param_timestamp (&valid_before), + GNUNET_PQ_query_param_string (cipher), + GNUNET_PQ_query_param_end + }; + + GNUNET_assert (pub->public_key.cipher == priv->private_key.cipher); + // TODO: Ensure pub->public_key.pub_key_hash matches the actual public key + // GNUNET_CRYPTO_hash (res, + // len, + // &bpk->pub_key_hash); + // GNUNET_assert (0 == + // GNUNET_memcmp (&pub_hash, + // &pub->public_key.)); + GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( + 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 " + "(token_family_serial" + ",pub" + ",h_pub" + ",priv" + ",valid_after" + ",valid_before" + ",cipher)" + " SELECT token_family_serial, $2, $3, $4, $5, $6, $7" + " FROM merchant_token_families" + " WHERE slug = $1"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "token_family_key_insert", + params); +}
\ No newline at end of file diff --git a/src/backenddb/pg_insert_token_family_key.h b/src/backenddb/pg_insert_token_family_key.h new file mode 100644 index 00000000..c4fc8d85 --- /dev/null +++ b/src/backenddb/pg_insert_token_family_key.h @@ -0,0 +1,46 @@ +/* + 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_insert_token_family_key.h + * @brief implementation of the insert_token_family_key function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H +#define PG_INSERT_TOKEN_FAMILY_KEY_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * @param cls closure + * @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 + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_insert_token_family_key (void *cls, + const char *token_family_slug, + const struct TALER_TokenFamilyPublicKey *pub, + const struct TALER_TokenFamilyPrivateKey *priv, + const struct GNUNET_TIME_Timestamp valid_after, + const 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 848b79a9..d2c651c9 100644 --- a/src/backenddb/pg_lookup_token_family.c +++ b/src/backenddb/pg_lookup_token_family.c @@ -105,9 +105,9 @@ TMH_PG_lookup_token_family (void *cls, if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { - if (strcmp(kind, "discount") == 0) + if (0 == strcmp(kind, "discount")) details->kind = TALER_MERCHANTDB_TFK_Discount; - else if (strcmp(kind, "subscription") == 0) + else if (0 == strcmp(kind, "subscription")) details->kind = TALER_MERCHANTDB_TFK_Subscription; else { diff --git a/src/backenddb/pg_lookup_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c new file mode 100644 index 00000000..f1fa75f3 --- /dev/null +++ b/src/backenddb/pg_lookup_token_family_key.c @@ -0,0 +1,161 @@ +/* + 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_key.c + * @brief Implementation of the lookup_token_family_key function for Postgres + * @author Christian Blättler + */ +#include "platform.h" +#include <gnunet/gnunet_pq_lib.h> +#include <gnunet/gnunet_time_lib.h> +#include <string.h> +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_lookup_token_family_key.h" +#include "pg_helper.h" + +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) +{ + 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_end + }; + + if (NULL == details) + { + struct GNUNET_PQ_ResultSpec rs_null[] = { + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_token_family_key", + params, + rs_null); + } + else + { + char *kind; + details->pub = NULL; + details->priv = NULL; + details->valid_after = GNUNET_TIME_UNIT_ZERO_TS; + details->valid_before = GNUNET_TIME_UNIT_ZERO_TS; + + 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 ("key_valid_after", + &details->valid_after), + NULL), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_timestamp ("key_valid_before", + &details->valid_before), + 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), + 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_string ("kind", + &kind), + GNUNET_PQ_result_spec_uint64 ("issued", + &details->token_family.issued), + GNUNET_PQ_result_spec_uint64 ("redeemed", + &details->token_family.redeemed), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + PREPARE (pg, + "lookup_token_family_key", + "SELECT" + " 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" + ",slug" + ",name" + ",description" + ",description_i18n" + ",merchant_token_families.valid_after" + ",merchant_token_families.valid_before" + ",duration" + ",kind" + ",issued" + ",redeemed" + " 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" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1" + " AND slug=$2" + " LIMIT 1"); + enum GNUNET_DB_QueryStatus qs; + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_token_family_key", + params, + rs); + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + 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); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + + /* TODO: How to handle multiple results? */ + + return qs; + } +}
\ No newline at end of file diff --git a/src/backenddb/pg_lookup_token_family_key.h b/src/backenddb/pg_lookup_token_family_key.h new file mode 100644 index 00000000..aa7335cf --- /dev/null +++ b/src/backenddb/pg_lookup_token_family_key.h @@ -0,0 +1,50 @@ +/* + 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_key.h + * @brief implementation of the lookup_token_family_key function for Postgres + * @author Christian Blättler + */ +#ifndef PG_LOOKUP_TOKEN_FAMILY_KEY_H +#define PG_LOOKUP_TOKEN_FAMILY_KEY_H + +#include <gnunet/gnunet_common.h> +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Lookup details about a particular token family key. + * + * @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[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); + + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index e09b4827..e5f3f2a1 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -138,6 +138,8 @@ #include "pg_lookup_token_families.h" #include "pg_delete_token_family.h" #include "pg_update_token_family.h" +#include "pg_insert_token_family_key.h" +#include "pg_lookup_token_family_key.h" /** @@ -575,9 +577,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_delete_token_family; plugin->update_token_family = &TMH_PG_update_token_family; + plugin->insert_token_family_key + = &TMH_PG_insert_token_family_key; + plugin->lookup_token_family_key + = &TMH_PG_lookup_token_family_key; plugin->update_deposit_confirmation_status = &TMH_PG_update_deposit_confirmation_status; + return plugin; } diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index c83b3c93..44fdc0ab 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -23,6 +23,8 @@ #ifndef TALER_MERCHANTDB_PLUGIN_H #define TALER_MERCHANTDB_PLUGIN_H +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_time_lib.h> #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_db_lib.h> #include <taler/taler_exchange_service.h> @@ -1094,17 +1096,17 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails /** * Token family public key. */ - struct TALER_TokenFamilyPublicKey pub; + struct TALER_TokenFamilyPublicKey *pub; /** - * Hash of the token family public key. + * Token family private key. */ - struct TALER_TokenFamilyPublicKeyHash pub_h; + struct TALER_TokenFamilyPrivateKey *priv; /** - * Token family private key. - */ - struct TALER_TokenFamilyPrivateKey priv; + * Details about the token family this key belongs to. + */ + struct TALER_MERCHANTDB_TokenFamilyDetails token_family; }; /** @@ -3240,7 +3242,7 @@ struct TALER_MERCHANTDB_Plugin * Insert details about a particular token family. * * @param cls closure - * @param instance_id instance to insert product for + * @param instance_id instance to insert token family for * @param token_family_slug slug of token family to insert * @param details the token family details to insert * @return database result code @@ -3252,6 +3254,49 @@ struct TALER_MERCHANTDB_Plugin const char *token_family_slug, const struct TALER_MERCHANTDB_TokenFamilyDetails *details); + + /** + * Lookup details about a particular token family key. + * + * @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[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 + (*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); + + + /** + * Insert details a key pair for a token family. + * + * @param cls closure + * @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 + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*insert_token_family_key)( + void *cls, + const char *token_family_slug, + const struct TALER_TokenFamilyPublicKey *pub, + const struct TALER_TokenFamilyPrivateKey *priv, + struct GNUNET_TIME_Timestamp valid_after, + struct GNUNET_TIME_Timestamp valid_before); + /** * Lookup deposits that are finished and awaiting a wire transfer. * |