diff options
author | Marcello Stanisci <marcello.stanisci@inria.fr> | 2015-11-05 17:56:12 +0100 |
---|---|---|
committer | Marcello Stanisci <marcello.stanisci@inria.fr> | 2015-11-05 17:56:12 +0100 |
commit | c1159ff547afffd135cde78c026dd35817d2f927 (patch) | |
tree | 9d0126a494b7018d4ad70e2c7d21de75ca6c3e8f /src/backend/taler-merchant-httpd_pay.c | |
parent | 45cd6f3e16d8411add89588673cd58873293c2b0 (diff) | |
parent | e55860d9139702f39ce2b61df5bb0e33e1f30a08 (diff) | |
download | merchant-c1159ff547afffd135cde78c026dd35817d2f927.tar.gz merchant-c1159ff547afffd135cde78c026dd35817d2f927.tar.bz2 merchant-c1159ff547afffd135cde78c026dd35817d2f927.zip |
Merge branch 'fix4013'
Conflicts:
src/frontend/index.html
Diffstat (limited to 'src/backend/taler-merchant-httpd_pay.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_pay.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c new file mode 100644 index 00000000..c96476ac --- /dev/null +++ b/src/backend/taler-merchant-httpd_pay.c @@ -0,0 +1,368 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file merchant/backend/taler-merchant-httpd.c + * @brief HTTP serving layer mainly intended to communicate with the frontend + * @author Marcello Stanisci + */ + +#include "platform.h" +#include <microhttpd.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <curl/curl.h> +#include <taler/taler_signatures.h> +#include <taler/taler_amount_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mint_service.h> +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" +#include "merchant_db.h" +#include "merchant.h" +#include "taler_merchant_lib.h" + +extern struct MERCHANT_Mint *mints; +extern const struct MERCHANT_WIREFORMAT_Sepa *wire; +extern PGconn *db_conn; +extern long long salt; +extern unsigned int nmints; +extern struct GNUNET_TIME_Relative edate_delay; +extern struct GNUNET_CRYPTO_EddsaPrivateKey privkey; + + + +/** + * Fetch the deposit fee related to the given coin aggregate. + * @param connection the connection to send an error response to + * @param coin_aggregate a coin "aggregate" is the JSON set of + * values contained in a single cell of the 'coins' array sent + * in a payment + * @param deposit_fee where to store the resulting deposit fee + * @param mint_index the index which points the chosen mint within + * the global 'mints' array + * @return GNUNET_OK if successful, GNUNET_NO if the data supplied + * is invalid (including the case when the key is not found), + * GNUNET_SYSERR upon internal errors + */ +int +deposit_fee_from_coin_aggregate (struct MHD_Connection *connection, + json_t *coin_aggregate, + struct TALER_Amount *deposit_fee, + unsigned int mint_index) +{ + int res; + const struct TALER_MINT_Keys *keys; + const struct TALER_MINT_DenomPublicKey *denom_details; + struct TALER_DenominationPublicKey denom; + + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_denomination_public_key ("denom_pub", &denom), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + coin_aggregate, + spec); + if (GNUNET_OK != res) + return res; /* may return GNUNET_NO */ + + /*printf ("mint %s (%d), pends: %d\n", + mints[mint_index].hostname, + mint_index, + mints[mint_index].pending);*/ + + if (1 == mints[mint_index].pending) + return GNUNET_SYSERR; + keys = TALER_MINT_get_keys (mints[mint_index].conn); + denom_details = TALER_MINT_get_denomination_key (keys, &denom); + if (NULL == denom_details) + { + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:o}", + "hint", "unknown denom to mint", + "denom_pub", TALER_json_from_rsa_public_key (denom.rsa_public_key)); + return GNUNET_NO; + } + *deposit_fee = denom_details->fee_deposit; + return GNUNET_OK; +} + + +/** + * Callback to handle a deposit permission's response. How does this behave if the mint + * goes offline during a call? + * @param cls closure (in our invocation it will be the transaction_id, so the cb can refer + * to the right DB row for this deposit permission) + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param proof the received JSON reply, should be kept as proof (and, in case of errors, + * be forwarded to the customer) + */ +void +deposit_cb (void *cls, unsigned int http_status, json_t *proof) +{ + if (MHD_HTTP_OK == http_status) + ; /* set pending to false in DB and notify the frontend with "OK" */ + else + ; /* set a timeout to retry */ +} + +/** + * Accomplish this payment. + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure + * (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a + * upload_data + * @return MHD result code + */ +int +MH_handler_pay (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + + json_t *root; + json_t *coins; + char *chosen_mint; + json_t *coin_aggregate; + json_t *wire_details; + unsigned int mint_index; /*a cell in the global array*/ + unsigned int coins_index; /*a cell in the global array*/ + unsigned int coins_cnt; /*a cell in the global array*/ + uint64_t transaction_id; + int res; + + struct TALER_MINT_DepositHandle *dh; + struct TALER_Amount max_fee; + struct TALER_Amount acc_fee; + struct TALER_Amount coin_fee; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute edate; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_TIME_Absolute refund_deadline; + struct TALER_MerchantPublicKeyP pubkey; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_DenominationPublicKey denom_pub; + struct TALER_DenominationSignature ub_sig; + struct TALER_CoinSpendSignatureP coin_sig; + struct GNUNET_HashCode h_contract; + + struct MERCHANT_DepositConfirmation *dc; + struct MERCHANT_DepositConfirmationCls *dccls; + + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_array ("coins", &coins), + TMH_PARSE_member_string ("mint", &chosen_mint), + TMH_PARSE_member_amount ("max_fee", &max_fee), + TMH_PARSE_member_time_abs ("timestamp", ×tamp), + TMH_PARSE_member_time_abs ("refund_deadline", &refund_deadline), + TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), + TMH_PARSE_member_fixed ("H_contract", &h_contract), + TMH_PARSE_MEMBER_END + }; + + struct TMH_PARSE_FieldSpecification coin_aggregate_spec[] = { + TMH_PARSE_member_amount ("f", &amount), + TMH_PARSE_member_fixed ("coin_pub", &coin_pub.eddsa_pub), + TMH_PARSE_member_denomination_public_key ("denom_pub", &denom_pub), + TMH_PARSE_member_denomination_signature ("ub_sig", &ub_sig), + TMH_PARSE_member_fixed ("coin_sig", &coin_sig.eddsa_signature), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + res = TMH_PARSE_json_data (connection, + root, + spec); + + if (GNUNET_YES != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + + /* 1 Check if the chosen mint is among the merchant's preferred. + + An error in this case could be due to: + + * the wallet indicated a non existent mint + * the wallet indicated a non trusted mint + + NOTE: by preventively checking this, the merchant + avoids getting HTTP response codes from random + websites that may mislead the wallet in the way + of managing the error. Of course, that protect the + merchant from POSTing coins to untrusted mints. + + */ + + for (mint_index = 0; mint_index <= nmints; mint_index++) + { + /* no mint found in array */ + if (mint_index == nmints) + { + mint_index = -1; + break; + } + + /* test it by checking public key */ + if (0 == strcmp (mints[mint_index].hostname, + chosen_mint)) + break; + + } + + if (-1 == mint_index) + return TMH_RESPONSE_reply_external_error (connection, "unknown mint"); + + /* no 'edate' from frontend. Generate it here; it will be timestamp + + a edate delay supplied in config file */ + if (NULL == json_object_get (root, "edate")) + { + edate = GNUNET_TIME_absolute_add (timestamp, edate_delay); + if (-1 == json_object_set (root, "edate", TALER_json_from_abs (edate))) + return MHD_NO; + } + + coins_cnt = json_array_size (coins); + + if (0 == coins_cnt) + return TMH_RESPONSE_reply_external_error (connection, "no coins given"); + + json_array_foreach (coins, coins_index, coin_aggregate) + { + res = deposit_fee_from_coin_aggregate (connection, + coin_aggregate, + &coin_fee, + mint_index); + if (GNUNET_NO == res) + return MHD_YES; + if (GNUNET_SYSERR == res) + return MHD_NO; + + if (0 == coins_index) + acc_fee = coin_fee; + else + TALER_amount_add (&acc_fee, + &acc_fee, + &coin_fee); + } + + + if (-1 == TALER_amount_cmp (&max_fee, &acc_fee)) + return MHD_HTTP_NOT_ACCEPTABLE; + + /* cutting off unneeded fields from deposit permission as + gotten from the wallet */ + if (-1 == json_object_del (root, "mint")) + return TMH_RESPONSE_reply_external_error (connection, + "malformed/non-existent 'mint' field"); + if (-1 == json_object_del (root, "coins")) + return TMH_RESPONSE_reply_external_error (connection, + "malformed/non-existent 'coins' field"); + + /* adding our public key to deposit permission */ + GNUNET_CRYPTO_eddsa_key_get_public (&privkey, &pubkey.eddsa_pub); + json_object_set_new (root, + "merchant_pub", + TALER_json_from_data (&pubkey, sizeof (pubkey))); + + wire_details = MERCHANT_get_wire_json (wire, salt); + /* since memory is zero'd out by GNUNET_malloc, any 'ackd' field will be + (implicitly) set to false */ + dc = GNUNET_malloc (coins_cnt * sizeof (struct MERCHANT_DepositConfirmation)); + if (NULL == dc) + return TMH_RESPONSE_reply_internal_error (connection, "memory failure"); + json_array_foreach (coins, coins_index, coin_aggregate) + { + + /* 3 For each coin in DB + + a. Generate a deposit permission + b. store it and its tid in DB + c. POST to the mint (see mint-lib for this) + (retry until getting a persisten state) + */ + + /* a */ + if (-1 == json_object_update (root, coin_aggregate)) + return TMH_RESPONSE_reply_internal_error (connection, + "deposit permission not generated for storing"); + + /* b */ + char *deposit_permission_str = json_dumps (root, JSON_COMPACT); + if (GNUNET_OK != MERCHANT_DB_store_deposit_permission (db_conn, + deposit_permission_str, + transaction_id, + 1, + mints[mint_index].hostname)) + return TMH_RESPONSE_reply_internal_error (connection, "internal DB failure"); + res = TMH_PARSE_json_data (connection, + coin_aggregate, + coin_aggregate_spec); + if (GNUNET_OK != res) + return res; /* may return GNUNET_NO */ + + dccls = GNUNET_malloc (sizeof (struct MERCHANT_DepositConfirmationCls)); + dccls->index = coins_index; + dccls->dc = dc; + + dh = TALER_MINT_deposit (mints[mint_index].conn, + &amount, + edate, + wire_details, + &h_contract, + &coin_pub, + &ub_sig, + &denom_pub, + timestamp, + transaction_id, + &pubkey, + refund_deadline, + &coin_sig, + deposit_cb, + dccls); /*may be destroyed by the time the cb gets called..*/ + if (NULL == dh) + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_SERVICE_UNAVAILABLE, + "{s:s, s:i}", + "mint", mints[mint_index].hostname, + "transaction_id", transaction_id); + } + + /* suspend connection until the last coin has been ack'd to the cb. + That last cb will finally resume the connection and send back a response */ + MHD_suspend_connection (connection); + return MHD_YES; + + /* 4 Return response code: success, or whatever data the + mint sent back regarding some bad coin */ +} |