diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_refund.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_refund.c | 509 |
1 files changed, 147 insertions, 362 deletions
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index 9fd9575f9..b8bcf7c60 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -32,7 +32,7 @@ #include "taler_mhd_lib.h" #include "taler-exchange-httpd_refund.h" #include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keystate.h" +#include "taler-exchange-httpd_keys.h" /** @@ -43,46 +43,63 @@ * @param refund details about the successful refund * @return MHD result code */ -static int +static MHD_RESULT reply_refund_success (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_EXCHANGEDB_RefundListEntry *refund) { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - struct TALER_RefundConfirmationPS rc = { - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), - .purpose.size = htonl (sizeof (rc)), - .h_contract_terms = refund->h_contract_terms, - .coin_pub = *coin_pub, - .merchant = refund->merchant_pub, - .rtransaction_id = GNUNET_htonll (refund->rtransaction_id) - }; - - TALER_amount_hton (&rc.refund_amount, - &refund->refund_amount); - TALER_amount_hton (&rc.refund_fee, - &refund->refund_fee); - if (GNUNET_OK != - TEH_KS_sign (&rc.purpose, - &pub, - &sig)) + enum TALER_ErrorCode ec; + + if (TALER_EC_NONE != + (ec = TALER_exchange_online_refund_confirmation_sign ( + &TEH_keys_exchange_sign_, + &refund->h_contract_terms, + coin_pub, + &refund->merchant_pub, + refund->rtransaction_id, + &refund->refund_amount, + &pub, + &sig))) { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no online signing key"); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); } - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:s, s:o, s:o}", - "status", "REFUND_OK", - "sig", GNUNET_JSON_from_data_auto (&sig), - "pub", GNUNET_JSON_from_data_auto (&pub)); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &pub)); } /** + * Closure for refund_transaction(). + */ +struct RefundContext +{ + /** + * Details about the deposit operation. + */ + const struct TALER_EXCHANGEDB_Refund *refund; + + /** + * Deposit fee of the coin. + */ + struct TALER_Amount deposit_fee; + + /** + * Unique ID of the coin in known_coins. + */ + uint64_t known_coin_id; +}; + + +/** * Execute a "/refund" transaction. Returns a confirmation that the * refund was successful, or a failure if we are not aware of a * matching /deposit or if it is too late to do the refund. @@ -95,7 +112,6 @@ reply_refund_success (struct MHD_Connection *connection, * * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *` * @param connection MHD request which triggered the transaction - * @param session database session to use * @param[out] mhd_ret set to MHD response status for @a connection, * if transaction failed (!) * @return transaction status @@ -103,231 +119,70 @@ reply_refund_success (struct MHD_Connection *connection, static enum GNUNET_DB_QueryStatus refund_transaction (void *cls, struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) + MHD_RESULT *mhd_ret) { - const struct TALER_EXCHANGEDB_Refund *refund = cls; - struct TALER_EXCHANGEDB_TransactionList *tl; - const struct TALER_EXCHANGEDB_DepositListEntry *dep; - const struct TALER_EXCHANGEDB_RefundListEntry *ref; + struct RefundContext *rctx = cls; + const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund; enum GNUNET_DB_QueryStatus qs; - int deposit_found; - int refund_found; + bool not_found; + bool refund_ok; + bool conflict; + bool gone; - tl = NULL; - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &refund->coin.coin_pub, - GNUNET_NO, - &tl); + /* Finally, store new refund data */ + qs = TEH_plugin->do_refund (TEH_plugin->cls, + refund, + &rctx->deposit_fee, + rctx->known_coin_id, + ¬_found, + &refund_ok, + &gone, + &conflict); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_COIN_NOT_FOUND, - "database transaction failure"); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do refund"); return qs; } - dep = NULL; - ref = NULL; - deposit_found = GNUNET_NO; - refund_found = GNUNET_NO; - for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl; - NULL != tlp; - tlp = tlp->next) - { - switch (tlp->type) - { - case TALER_EXCHANGEDB_TT_DEPOSIT: - if (GNUNET_NO == deposit_found) - { - if ( (0 == memcmp (&tlp->details.deposit->merchant_pub, - &refund->details.merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.deposit->h_contract_terms, - &refund->details.h_contract_terms, - sizeof (struct GNUNET_HashCode))) ) - { - dep = tlp->details.deposit; - deposit_found = GNUNET_YES; - break; - } - } - break; - case TALER_EXCHANGEDB_TT_MELT: - /* Melts cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_REFUND: - if (GNUNET_NO == refund_found) - { - /* First, check if existing refund request is identical */ - if ( (0 == memcmp (&tlp->details.refund->merchant_pub, - &refund->details.merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.refund->h_contract_terms, - &refund->details.h_contract_terms, - sizeof (struct GNUNET_HashCode))) && - (tlp->details.refund->rtransaction_id == - refund->details.rtransaction_id) ) - { - ref = tlp->details.refund; - refund_found = GNUNET_YES; - break; - } - /* Second, check if existing refund request conflicts */ - if ( (0 == memcmp (&tlp->details.refund->merchant_pub, - &refund->details.merchant_pub, - sizeof (struct TALER_MerchantPublicKeyP))) && - (0 == memcmp (&tlp->details.refund->h_contract_terms, - &refund->details.h_contract_terms, - sizeof (struct GNUNET_HashCode))) && - (tlp->details.refund->rtransaction_id != - refund->details.rtransaction_id) ) - { - refund_found = GNUNET_SYSERR; - /* NOTE: Alternatively we could total up all existing - refunds and check if the sum still permits the - refund requested (thus allowing multiple, partial - refunds). Fow now, we keep it simple. */ - break; - } - } - break; - case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP: - /* Recoups cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_RECOUP: - /* Recoups cannot be refunded, ignore here */ - break; - case TALER_EXCHANGEDB_TT_RECOUP_REFRESH: - /* Recoups cannot be refunded, ignore here */ - break; - } - } - /* handle if deposit was NOT found */ - if (GNUNET_NO == deposit_found) + + if (gone) { - TALER_LOG_WARNING ("Deposit to /refund was not found\n"); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_DEPOSIT_NOT_FOUND, - "deposit unknown"); + MHD_HTTP_GONE, + TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID, + NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - /* handle if conflicting refund found */ - if (GNUNET_SYSERR == refund_found) + if (conflict) { - *mhd_ret = TALER_MHD_reply_json_pack ( + GNUNET_break_op (0); + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o}", - "hint", "conflicting refund", - "code", (json_int_t) TALER_EC_REFUND_CONFLICT, - "history", TEH_RESPONSE_compile_transaction_history ( - &refund->coin.coin_pub, - tl)); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); + TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, + &refund->coin.denom_pub_hash, + &refund->coin.coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - /* handle if identical refund found */ - if (GNUNET_YES == refund_found) + if (not_found) { - /* /refund already done, simply re-transmit confirmation */ - *mhd_ret = reply_refund_success (connection, - &refund->coin.coin_pub, - ref); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* check currency is compatible */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&refund->details.refund_amount, - &dep->amount_with_fee)) || - (GNUNET_YES != - TALER_amount_cmp_currency (&refund->details.refund_fee, - &dep->deposit_fee)) ) - { - GNUNET_break_op (0); /* currency mismatch */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_CURRENCY_MISMATCH, - "currencies involved do not match"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* check if we already send the money for the /deposit */ - qs = TEH_plugin->test_deposit_done (TEH_plugin->cls, - session, - &refund->coin.coin_pub, - &dep->merchant_pub, - &dep->h_contract_terms, - &dep->h_wire); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - /* Internal error, we first had the deposit in the history, - but now it is gone? */ - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFUND_DB_INCONSISTENT, - "database inconsistent (deposit data became inaccessible during transaction)"); - return qs; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - return qs; /* go and retry */ - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - /* money was already transferred to merchant, can no longer refund */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_GONE, - TALER_EC_REFUND_MERCHANT_ALREADY_PAID, - "money already sent to merchant"); + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND, + NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - - /* check refund amount is sufficiently low */ - if (1 == TALER_amount_cmp (&refund->details.refund_amount, - &dep->amount_with_fee) ) + if (! refund_ok) { - GNUNET_break_op (0); /* cannot refund more than original value */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_INSUFFICIENT_FUNDS, - "refund requested exceeds original value"); + *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT, + &refund->coin.denom_pub_hash, + &refund->coin.coin_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - - /* Finally, store new refund data */ - qs = TEH_plugin->insert_refund (TEH_plugin->cls, - session, - refund); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - TALER_LOG_WARNING ("Failed to store /refund information in database\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFUND_STORE_DB_ERROR, - "could not persist store information"); - return qs; - } - /* Success or soft failure */ return qs; } @@ -339,42 +194,31 @@ refund_transaction (void *cls, * the fee structure, so this is not done here. * * @param connection the MHD connection to handle - * @param refund information about the refund + * @param[in,out] refund information about the refund * @return MHD result code */ -static int +static MHD_RESULT verify_and_execute_refund (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_Refund *refund) + struct TALER_EXCHANGEDB_Refund *refund) { - struct GNUNET_HashCode denom_hash; - struct TALER_Amount expect_fee; + struct RefundContext rctx = { + .refund = refund + }; + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_merchant_refund_verify (&refund->coin.coin_pub, + &refund->details.h_contract_terms, + refund->details.rtransaction_id, + &refund->details.refund_amount, + &refund->details.merchant_pub, + &refund->details.merchant_sig)) { - struct TALER_RefundRequestPS rr = { - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), - .purpose.size = htonl (sizeof (rr)), - .h_contract_terms = refund->details.h_contract_terms, - .coin_pub = refund->coin.coin_pub, - .merchant = refund->details.merchant_pub, - .rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id) - }; - - TALER_amount_hton (&rr.refund_amount, - &refund->details.refund_amount); - TALER_amount_hton (&rr.refund_fee, - &refund->details.refund_fee); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr.purpose, - &refund->details.merchant_sig.eddsa_sig, - &refund->details.merchant_pub.eddsa_pub)) - { - TALER_LOG_WARNING ("Invalid signature on refund request\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID, - "merchant_sig"); - } + TALER_LOG_WARNING ("Invalid signature on refund request\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID, + NULL); } /* Fetch the coin's denomination (hash) */ @@ -382,100 +226,57 @@ verify_and_execute_refund (struct MHD_Connection *connection, enum GNUNET_DB_QueryStatus qs; qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls, - NULL, &refund->coin.coin_pub, - &denom_hash); + &rctx.known_coin_id, + &refund->coin.denom_pub_hash); if (0 > qs) { + MHD_RESULT res; + char *dhs; + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_COIN_NOT_FOUND, - "denomination of coin to be refunded not found in DB"); + dhs = GNUNET_STRINGS_data_to_string_alloc ( + &refund->coin.denom_pub_hash, + sizeof (refund->coin.denom_pub_hash)); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND, + dhs); + GNUNET_free (dhs); + return res; } } { - struct TEH_KS_StateHandle *key_state; - - key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); - if (NULL == key_state) - { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } /* Obtain information about the coin's denomination! */ - { - struct TALER_EXCHANGEDB_DenominationKey *dki; - unsigned int hc; - enum TALER_ErrorCode ec; - - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &denom_hash, - TEH_KS_DKU_DEPOSIT, - &ec, - &hc); - if (NULL == dki) - { - /* DKI not found, but we do have a coin with this DK in our database; - not good... */ - GNUNET_break (0); - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - hc, - ec, - "denomination not found, but coin known"); - } - TALER_amount_ntoh (&expect_fee, - &dki->issue.properties.fee_refund); - } - TEH_KS_release (key_state); - } + struct TEH_DenominationKey *dk; + MHD_RESULT mret; - /* Check refund fee matches fee of denomination key! */ - if (GNUNET_YES != - TALER_amount_cmp_currency (&expect_fee, - &refund->details.refund_fee) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_CURRENCY_MISMATCH, - "refund_fee"); - } - { - int fee_cmp; - - fee_cmp = TALER_amount_cmp (&refund->details.refund_fee, - &expect_fee); - if (-1 == fee_cmp) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_TOO_LOW, - "refund_fee"); - } - if (1 == fee_cmp) + dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash, + connection, + &mret); + if (NULL == dk) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Refund fee proposed by merchant is higher than necessary.\n"); + /* DKI not found, but we do have a coin with this DK in our database; + not good... */ + GNUNET_break (0); + return mret; } + refund->details.refund_fee = dk->meta.fees.refund; + rctx.deposit_fee = dk->meta.fees.deposit; } - /* Finally run the actual transaction logic */ { - int mhd_ret; + MHD_RESULT mhd_ret; if (GNUNET_OK != TEH_DB_run_transaction (connection, "run refund", + TEH_MT_REQUEST_OTHER, &mhd_ret, &refund_transaction, - (void *) refund)) + &rctx)) { return mhd_ret; } @@ -497,27 +298,32 @@ verify_and_execute_refund (struct MHD_Connection *connection, * @param root uploaded JSON data * @return MHD result code */ -int +MHD_RESULT TEH_handler_refund (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const json_t *root) { - struct TALER_EXCHANGEDB_Refund refund; + struct TALER_EXCHANGEDB_Refund refund = { + .details.refund_fee.currency = {0} /* set to invalid, just to be sure */ + }; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("refund_amount", &refund.details.refund_amount), - TALER_JSON_spec_amount ("refund_fee", &refund.details.refund_fee), + TALER_JSON_spec_amount ("refund_amount", + TEH_currency, + &refund.details.refund_amount), GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &refund.details.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.details.merchant_pub), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &refund.details.merchant_pub), GNUNET_JSON_spec_uint64 ("rtransaction_id", &refund.details.rtransaction_id), - GNUNET_JSON_spec_fixed_auto ("merchant_sig", &refund.details.merchant_sig), + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &refund.details.merchant_sig), GNUNET_JSON_spec_end () }; refund.coin.coin_pub = *coin_pub; { - int res; + enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, root, @@ -527,29 +333,8 @@ TEH_handler_refund (struct MHD_Connection *connection, if (GNUNET_NO == res) return MHD_YES; /* failure */ } - if (GNUNET_YES != - TALER_amount_cmp_currency (&refund.details.refund_amount, - &refund.details.refund_fee) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_CURRENCY_MISMATCH, - "refund_amount or refund_fee"); - } - if (-1 == TALER_amount_cmp (&refund.details.refund_amount, - &refund.details.refund_fee) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_ABOVE_AMOUNT, - "refund_amount"); - } { - int res; + MHD_RESULT res; res = verify_and_execute_refund (connection, &refund); |