implementing parsing of /refund requests

This commit is contained in:
Christian Grothoff 2016-04-20 02:50:52 +02:00
parent f693e25793
commit edd31c7415
9 changed files with 441 additions and 85 deletions

View File

@ -35,6 +35,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
taler-exchange-httpd_parsing.c taler-exchange-httpd_parsing.h \
taler-exchange-httpd_refresh.c taler-exchange-httpd_refresh.h \
taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
taler-exchange-httpd_reserve.c taler-exchange-httpd_reserve.h \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
taler-exchange-httpd_tracking.c taler-exchange-httpd_tracking.h \

View File

@ -30,6 +30,7 @@
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_admin.h"
#include "taler-exchange-httpd_deposit.h"
#include "taler-exchange-httpd_refund.h"
#include "taler-exchange-httpd_reserve.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_refresh.h"
@ -200,6 +201,14 @@ handle_mhd_request (void *cls,
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
/* Refunding coins */
{ "/refund", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,
&TMH_REFUND_handler_refund, MHD_HTTP_OK },
{ "/refund", NULL, "text/plain",
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
/* Dealing with change */
{ "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015 GNUnet e.V.
Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@ -21,9 +21,6 @@
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*
* TODO:
* - ugly if-construction for deposit type
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
@ -119,28 +116,37 @@ verify_and_execute_deposit (struct MHD_Connection *connection,
/**
* Handle a "/deposit" request. This function parses the
* JSON information and then calls #verify_and_execute_deposit()
* to verify the signatures and execute the deposit.
* Handle a "/deposit" request. Parses the JSON, and, if successful,
* passes the JSON data to #verify_and_execute_deposit() to further
* check the details of the operation specified. If everything checks
* out, this will ultimately lead to the "/deposit" being executed, or
* rejected.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param root root of the posted JSON
* @param amount how much should be deposited
* @param wire json describing the wire details (?)
* @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
*/
static int
parse_and_handle_deposit_request (struct MHD_Connection *connection,
const json_t *root,
const struct TALER_Amount *amount,
json_t *wire)
int
TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
json_t *json;
int res;
json_t *wire;
struct TALER_EXCHANGEDB_Deposit deposit;
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
struct TMH_KS_StateHandle *ks;
struct GNUNET_HashCode my_h_wire;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("wire", &wire),
TALER_JSON_spec_amount ("f", &amount),
TALER_JSON_spec_denomination_public_key ("denom_pub", &deposit.coin.denom_pub),
TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub),
@ -155,10 +161,20 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
GNUNET_JSON_spec_end ()
};
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
memset (&deposit, 0, sizeof (deposit));
res = TMH_PARSE_json_data (connection,
root,
json,
spec);
json_decref (json);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
@ -205,12 +221,12 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
&dki->issue.properties.fee_deposit);
TMH_KS_release (ks);
deposit.wire = wire;
deposit.amount_with_fee = *amount;
deposit.amount_with_fee = amount;
if (-1 == TALER_amount_cmp (&deposit.amount_with_fee,
&deposit.deposit_fee))
{
/* Total amount smaller than fee, invalid */
GNUNET_JSON_parse_free (spec);
GNUNET_JSON_parse_free (spec);
return TMH_RESPONSE_reply_arg_invalid (connection,
"f");
}
@ -221,64 +237,4 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection,
}
/**
* Handle a "/deposit" request. Parses the JSON in the post to find
* the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if
* successful, passes the JSON data to
* #parse_and_handle_deposit_request() to further check the details
* of the operation specified in the "wire" field of the JSON data.
* If everything checks out, this will ultimately lead to the
* "/deposit" being executed, or rejected.
*
* @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
TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
json_t *json;
json_t *wire;
int res;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("wire", &wire),
TALER_JSON_spec_amount ("f", &amount),
GNUNET_JSON_spec_end ()
};
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
res = TMH_PARSE_json_data (connection,
json,
spec);
if (GNUNET_OK != res)
{
json_decref (json);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
res = parse_and_handle_deposit_request (connection,
json,
&amount,
wire);
GNUNET_JSON_parse_free (spec);
json_decref (json);
return res;
}
/* end of taler-exchange-httpd_deposit.c */

View File

@ -29,13 +29,9 @@
/**
* Handle a "/deposit" request. Parses the JSON in the post to find
* the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if
* successful, passes the JSON data to
* #parse_and_handle_deposit_request() to further check the details
* of the operation specified in the "wire" field of the JSON data.
* If everything checks out, this will ultimately lead to the
* "/deposit" being executed, or rejected.
* Handle a "/deposit" request. Parses the JSON, and, if successful,
* checks the signatures. If everything checks out, this will
* ultimately lead to the "/deposit" being executed, or rejected.
*
* @param rh context of the handler
* @param connection the MHD connection to handle

View File

@ -0,0 +1,146 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refund.c
* @brief Handle /refund requests; parses the POST and JSON and
* verifies the coin signature before handing things off
* to the database.
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_refund.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
#include "taler-exchange-httpd_validation.h"
/**
* We have parsed the JSON information about the refund, do some basic
* sanity checks (especially that the signature on the coin is valid)
* and then execute the refund. Note that we need the DB to check
* the fee structure, so this is not done here.
*
* @param connection the MHD connection to handle
* @param refund information about the refund
* @return MHD result code
*/
static int
verify_and_execute_refund (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_Refund *refund)
{
struct TALER_RefundRequestPS dr;
dr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND);
dr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS));
dr.h_contract = refund->h_contract;
dr.transaction_id = GNUNET_htonll (refund->transaction_id);
dr.rtransaction_id = GNUNET_htonll (refund->rtransaction_id);
TALER_amount_hton (&dr.refund_amount,
&refund->refund_amount);
TALER_amount_hton (&dr.refund_fee,
&refund->refund_fee);
dr.merchant = refund->merchant_pub;
dr.coin_pub = refund->coin.coin_pub;
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
&dr.purpose,
&refund->merchant_sig.eddsa_sig,
&refund->merchant_pub.eddsa_pub))
{
TALER_LOG_WARNING ("Invalid signature on /refund request\n");
return TMH_RESPONSE_reply_signature_invalid (connection,
"merchant_sig");
}
#if 1
GNUNET_break (0); // FIXME: not implemented
return MHD_NO;
#else
return TMH_DB_execute_refund (connection,
refund);
#endif
}
/**
* Handle a "/refund" request. Parses the JSON, and, if successful,
* passes the JSON data to #parse_and_handle_refund_request() to
* further check the details of the operation specified. If
* everything checks out, this will ultimately lead to the "/refund"
* being executed, or rejected.
*
* @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
TMH_REFUND_handler_refund (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
json_t *json;
int res;
struct TALER_EXCHANGEDB_Refund refund;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("refund_amount", &refund.refund_amount),
TALER_JSON_spec_amount ("refund_fee", &refund.refund_fee),
GNUNET_JSON_spec_fixed_auto ("H_contract", &refund.h_contract),
GNUNET_JSON_spec_uint64 ("transaction_id", &refund.transaction_id),
GNUNET_JSON_spec_fixed_auto ("coin_pub", &refund.coin.coin_pub),
GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.merchant_pub),
GNUNET_JSON_spec_uint64 ("rtransaction_id", &refund.rtransaction_id),
GNUNET_JSON_spec_fixed_auto ("merchant_sig", &refund.merchant_sig),
GNUNET_JSON_spec_end ()
};
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&json);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == json) )
return MHD_YES;
res = TMH_PARSE_json_data (connection,
json,
spec);
json_decref (json);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
return MHD_YES; /* failure */
res = verify_and_execute_refund (connection,
&refund);
GNUNET_JSON_parse_free (spec);
return res;
}
/* end of taler-exchange-httpd_refund.c */

View File

@ -0,0 +1,52 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015, 2016 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refund.h
* @brief Handle /refund requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_REFUND_H
#define TALER_EXCHANGE_HTTPD_REFUND_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/refund" request. Parses the JSON, and, if successful,
* passes the JSON data to #parse_and_handle_refund_request() to
* further check the details of the operation specified. If
* everything checks out, this will ultimately lead to the "/refund"
* being executed, or rejected.
*
* @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
TMH_REFUND_handler_refund (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
#endif

View File

@ -465,6 +465,83 @@ void
TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit);
/* ********************* /refund *********************** */
/**
* @brief A Refund Handle
*/
struct TALER_EXCHANGE_RefundHandle;
/**
* Callbacks of this type are used to serve the result of submitting a
* deposit permission request to a exchange.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit;
* 0 if the exchange's reply is bogus (fails to follow the protocol)
* @param obj the received JSON reply, should be kept as proof (and, in particular,
* be forwarded to the customer)
*/
typedef void
(*TALER_EXCHANGE_RefundResultCallback) (void *cls,
unsigned int http_status,
const json_t *obj);
/**
* Submit a refund request to the exchange and get the exchange's
* response. This API is used by a merchant. Note that
* while we return the response verbatim to the caller for further
* processing, we do already verify that the response is well-formed
* (i.e. that signatures included in the response are all valid). If
* the exchange's reply is not well-formed, we return an HTTP status code
* of zero to @a cb.
*
* The @a exchange must be ready to operate (i.e. have
* finished processing the /keys reply). If this check fails, we do
* NOT initiate the transaction with the exchange and instead return NULL.
*
* @param exchange the exchange handle; the exchange must be ready to operate
* @param amount the amount to be refunded; must be larger than the refund fee
* (as that fee is still being subtracted), and smaller than the amount
* (with deposit fee) of the original deposit contribution of this coin
* @param h_contract hash of the contact of the merchant with the customer that is being refunded
* @param transaction_id transaction id for the transaction being refunded, must match @a h_contract
* @param coin_pub coins public key of the coin from the original deposit operation
* @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation);
* this is needed as we may first do a partial refund and later a full refund. If both
* refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint
* refund requests different (as requests are idempotent and otherwise the 2nd refund might not work).
* @param merchant_priv the private key of the merchant, used to generate signature for refund request
* @param cb the callback to call when a reply for this request is available
* @param cb_cls closure for the above callback
* @return a handle for this request; NULL if the inputs are invalid (i.e.
* signatures fail to verify). In this case, the callback is not called.
*/
struct TALER_EXCHANGE_RefundHandle *
TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_Amount *amount,
const struct GNUNET_HashCode *h_contract,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t rtransaction_id,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
TALER_EXCHANGE_RefundResultCallback cb,
void *cb_cls);
/**
* Cancel a refund permission request. This function cannot be used
* on a request handle if a response is already served for it. If
* this function is called, the refund may or may not have happened.
* However, it is fine to try to refund the coin a second time.
*
* @param refund the refund request handle
*/
void
TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund);
/* ********************* /reserve/status *********************** */

View File

@ -298,6 +298,67 @@ struct TALER_EXCHANGEDB_Deposit
};
/**
* @brief Specification for a /refund operation. The combination of
* the coin's public key, the merchant's public key and the
* transaction ID must be unique. While a coin can (theoretically) be
* deposited at the same merchant twice (with partial spending), the
* merchant must either use a different public key or a different
* transaction ID for the two transactions. The same goes for
* refunds, hence we also have a "rtransaction" ID which is disjoint
* from the transaction ID. The same coin must not be used twice at
* the same merchant for the same transaction or rtransaction ID.
*/
struct TALER_EXCHANGEDB_Refund
{
/**
* Information about the coin that is being refunded.
*/
struct TALER_CoinPublicInfo coin;
/**
* Public key of the merchant.
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Signature from the merchant affirming the refund.
*/
struct TALER_MerchantSignatureP merchant_sig;
/**
* Hash over the contract between merchant and customer
* (remains unknown to the Exchange).
*/
struct GNUNET_HashCode h_contract;
/**
* Merchant-generated transaction ID to detect duplicate
* transactions, of the original transaction that is being
* refunded.
*/
uint64_t transaction_id;
/**
* Merchant-generated REFUND transaction ID to detect duplicate
* refunds.
*/
uint64_t rtransaction_id;
/**
* Fraction of the original deposit's value to be refunded, including
* refund fee (if any). The coin is identified by @e coin_pub.
*/
struct TALER_Amount refund_amount;
/**
* Refund fee to be covered by the customer.
*/
struct TALER_Amount refund_fee;
};
/**
* @brief Global information for a refreshing session. Includes
* dimensions of the operation, security parameters and

View File

@ -395,6 +395,64 @@ struct TALER_DepositConfirmationPS
};
/**
* @brief Format used to generate the signature on a request to refund
* a coin into the account of the customer.
*/
struct TALER_RefundRequestPS
{
/**
* Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
/**
* Hash over the contract which is being refunded.
*/
struct GNUNET_HashCode h_contract GNUNET_PACKED;
/**
* Merchant-generated transaction ID of the orginal transaction.
*/
uint64_t transaction_id GNUNET_PACKED;
/**
* The coin's public key. This is the value that must have been
* signed (blindly) by the Exchange.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* The Merchant's public key. Allows the merchant to later refund
* the transaction or to inquire about the wire transfer identifier.
*/
struct TALER_MerchantPublicKeyP merchant;
/**
* Merchant-generated transaction ID for the refund.
*/
uint64_t rtransaction_id GNUNET_PACKED;
/**
* Amount to be refunded, including refund fee charged by the
* exchange to the customer.
*/
struct TALER_AmountNBO refund_amount;
/**
* Refund fee charged by the exchange. This must match the
* Exchange's denomination key's refund fee. If the client puts in
* an invalid refund fee (too high or too low) that does not match
* the Exchange's denomination key, the refund operation is invalid
* and will be rejected by the exchange. The @e amount_with_fee
* minus the @e refund_fee is the amount that will be credited to
* the original coin.
*/
struct TALER_AmountNBO refund_fee;
};
/**
* @brief Message signed by a coin to indicate that the coin should be
* melted.