/*
This file is part of TALER
(C) 2014-2017 Taler Systems SA
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 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
*/
/**
* @file backend/taler-merchant-httpd_tip-authorize.c
* @brief implement API for authorizing tips to be paid to visitors
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_tip-authorize.h"
#include "taler-merchant-httpd_tip-reserve-helper.h"
struct TipAuthContext
{
/**
* This field MUST be first for handle_mhd_completion_callback() to work
* when it treats this struct as a `struct TM_HandlerContext`.
*/
struct TM_HandlerContext hc;
/**
* Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
*/
void *json_parse_context;
/**
* Justification to use.
*/
const char *justification;
/**
* JSON request received.
*/
json_t *root;
/**
* Context for checking the tipping reserve's status.
*/
struct TMH_CheckTipReserve ctr;
/**
* Tip amount requested.
*/
struct TALER_Amount amount;
/**
* Flag set to #GNUNET_YES when we have tried /reserve/status of the
* tipping reserve already.
*/
int checked_status;
/**
* Flag set to #GNUNET_YES when we have parsed the incoming JSON already.
*/
int parsed_json;
};
/**
* Custom cleanup routine for a `struct TipAuthContext`.
*
* @param hc the `struct TMH_JsonParseContext` to clean up.
*/
static void
cleanup_tac (struct TM_HandlerContext *hc)
{
struct TipAuthContext *tac = (struct TipAuthContext *) hc;
if (NULL != tac->root)
{
json_decref (tac->root);
tac->root = NULL;
}
TMH_check_tip_reserve_cleanup (&tac->ctr);
TALER_MHD_parse_post_cleanup_callback (tac->json_parse_context);
GNUNET_free (tac);
}
/**
* Handle a "/tip-authorize" request.
*
* @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
* @param mi merchant backend instance, never NULL
* @return MHD result code
*/
MHD_RESULT
MH_handler_tip_authorize (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size,
struct MerchantInstance *mi)
{
struct TipAuthContext *tac;
enum GNUNET_GenericReturnValue res;
enum TALER_ErrorCode ec;
struct GNUNET_TIME_Absolute expiration;
struct GNUNET_HashCode tip_id;
json_t *extra;
if (NULL == *connection_cls)
{
tac = GNUNET_new (struct TipAuthContext);
tac->hc.cc = &cleanup_tac;
tac->ctr.connection = connection;
*connection_cls = tac;
}
else
{
tac = *connection_cls;
}
if (NULL != tac->ctr.response)
{
MHD_RESULT ret;
ret = MHD_queue_response (connection,
tac->ctr.response_code,
tac->ctr.response);
MHD_destroy_response (tac->ctr.response);
tac->ctr.response = NULL;
return ret;
}
if (GNUNET_NO == tac->parsed_json)
{
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount ("amount", &tac->amount),
GNUNET_JSON_spec_string ("justification", &tac->justification),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_post_json (connection,
&tac->json_parse_context,
upload_data,
upload_data_size,
&tac->root);
if (GNUNET_SYSERR == res)
return MHD_NO;
/* the POST's body has to be further fetched */
if ( (GNUNET_NO == res) ||
(NULL == tac->root) )
return MHD_YES;
res = TALER_MHD_parse_json_data (connection,
tac->root,
spec);
if (GNUNET_YES != res)
{
GNUNET_break_op (0);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
tac->parsed_json = GNUNET_YES;
}
if (NULL == mi->tip_exchange)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Instance `%s' not configured for tipping\n",
mi->id);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_PRECONDITION_FAILED,
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP,
"exchange for tipping not configured for the instance");
}
tac->ctr.reserve_priv = mi->tip_reserve;
extra = json_object_get (tac->root, "extra");
if (NULL == extra)
extra = json_object ();
else
json_incref (extra);
db->preflight (db->cls);
ec = db->authorize_tip_TR (db->cls,
tac->justification,
extra,
&tac->amount,
&mi->tip_reserve,
mi->tip_exchange,
&expiration,
&tip_id);
json_decref (extra);
/* If we have insufficient funds according to OUR database,
check with exchange to see if the reserve has been topped up
in the meantime (or if tips were not withdrawn yet). */
if ( (TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS == ec) &&
(GNUNET_NO == tac->checked_status) )
{
tac->checked_status = GNUNET_YES;
tac->ctr.none_authorized = GNUNET_YES;
TMH_check_tip_reserve (&tac->ctr,
mi->tip_exchange);
return MHD_YES;
}
/* handle irrecoverable errors */
if (TALER_EC_NONE != ec)
{
unsigned int rc;
const char *msg;
switch (ec)
{
case TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS:
rc = MHD_HTTP_PRECONDITION_FAILED;
msg = "Failed to approve tip: merchant has insufficient tipping funds";
break;
case TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED:
msg = "Failed to approve tip: merchant's tipping reserve expired";
rc = MHD_HTTP_PRECONDITION_FAILED;
break;
case TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN:
msg = "Failed to approve tip: merchant's tipping reserve does not exist";
rc = MHD_HTTP_SERVICE_UNAVAILABLE;
break;
default:
rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
msg = "Failed to approve tip: internal server error";
break;
}
return TALER_MHD_reply_with_error (connection,
rc,
ec,
msg);
}
/* generate success response */
{
char *taler_tip_uri;
const char *host;
const char *forwarded_host;
const char *uri_path;
const char *uri_instance_id;
struct GNUNET_CRYPTO_HashAsciiEncoded hash_enc;
host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Host");
forwarded_host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
"X-Forwarded-Host");
uri_path = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
"X-Forwarded-Prefix");
if (NULL == uri_path)
uri_path = "-";
if (NULL != forwarded_host)
host = forwarded_host;
if (NULL == host)
{
/* Should never happen, at last the host header should be defined */
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_INTERNAL_INVARIANT_FAILURE,
"unable to identify backend host");
}
if (0 == strcmp (mi->id, "default"))
uri_instance_id = "-";
else
uri_instance_id = mi->id;
GNUNET_CRYPTO_hash_to_enc (&tip_id, &hash_enc);
GNUNET_assert (0 < GNUNET_asprintf (&taler_tip_uri,
"taler://tip/%s/%s/%s/%s",
host,
uri_path,
uri_instance_id,
hash_enc.encoding));
return TALER_MHD_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:s, s:s}",
"taler_tip_uri", taler_tip_uri,
"tip_id", hash_enc.encoding);
}
}
/* end of taler-merchant-httpd_tip-authorize.c */