/*
This file is part of TALER
Copyright (C) 2014-2023 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
*/
/**
* @file taler-merchant-httpd.h
* @brief HTTP serving layer mainly intended to communicate with the frontend
* @author Marcello Stanisci
*/
#ifndef TALER_MERCHANT_HTTPD_H
#define TALER_MERCHANT_HTTPD_H
#include "platform.h"
#include "taler_merchantdb_lib.h"
#include
#include
#include "taler_merchant_bank_lib.h"
/**
* Shorthand for exit jumps.
*/
#define EXITIF(cond) \
do { \
if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
} while (0)
/**
* Supported wire method. Kept in a DLL.
*/
struct TMH_WireMethod
{
/**
* Next entry in DLL.
*/
struct TMH_WireMethod *next;
/**
* Previous entry in DLL.
*/
struct TMH_WireMethod *prev;
/**
* Which wire method / payment target identifier is @e payto_uri using?
*/
char *wire_method;
/**
* Wire details for this instance
*/
char *payto_uri;
/**
* Salt to use when computing @e h_wire from @e payto_uri.
*/
struct TALER_WireSaltP wire_salt;
/**
* Hash of our wire format details as given in @e payto_uri
*/
struct TALER_MerchantWireHashP h_wire;
/**
* Base URL of the credit facade.
*/
char *credit_facade_url;
/**
* Authentication data to access the credit facade.
* May be uninitialized if not provided by the client.
*/
json_t *credit_facade_credentials;
/**
* Is this wire method active (should it be included in new contracts)?
*/
bool active;
/**
* Are we currently in a transaction to delete this account?
*/
bool deleting;
/**
* Are we currently in a transaction to enable this account?
*/
bool enabling;
};
/**
* A pending GET /orders request that is in long polling mode.
*/
struct TMH_PendingOrder;
/**
* Information that defines a merchant "instance". That way, a single
* backend can account for several merchants, as used to do in donation
* shops
*/
struct TMH_MerchantInstance
{
/**
* Next entry in DLL.
*/
struct TMH_WireMethod *wm_head;
/**
* Previous entry in DLL.
*/
struct TMH_WireMethod *wm_tail;
/**
* Hash of the instance ID, key for the DHT.
*/
struct GNUNET_HashCode h_instance;
/**
* Head of DLL of long-polling GET /orders requests of this instance.
*/
struct TMH_PendingOrder *po_head;
/**
* Tail of DLL of long-polling GET /orders requests of this instance.
*/
struct TMH_PendingOrder *po_tail;
/**
* Database event we are waiting on to be resuming
* long-polling requests from the @e po_head.
*/
struct GNUNET_DB_EventHandler *po_eh;
/**
* Merchant's private key.
*/
struct TALER_MerchantPrivateKeyP merchant_priv;
/**
* Merchant's public key
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* General settings for an instance.
*/
struct TALER_MERCHANTDB_InstanceSettings settings;
/**
* General settings for an instance.
*/
struct TALER_MERCHANTDB_InstanceAuthSettings auth;
/**
* Reference counter on this structure. Only destroyed if the
* counter hits zero.
*/
unsigned int rc;
/**
* True if this instance was deleted (but not yet purged).
*/
bool deleted;
/**
* The authentication settings for this instance
* were changed via the command-line. Do not check
* against the DB value when updating the auth token.
*/
bool auth_override;
};
GNUNET_NETWORK_STRUCT_BEGIN
/**
* Event triggered when an order is paid.
*/
struct TMH_OrderPayEventP
{
/**
* Type is #TALER_DBEVENT_MERCHANT_ORDER_PAID
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Always zero (for alignment).
*/
uint32_t reserved GNUNET_PACKED;
/**
* Merchant's public key
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Hash of the order ID.
*/
struct GNUNET_HashCode h_order_id;
};
/**
* Event triggered when a fulfillment URL is
* bound to a session (as paid).
*/
struct TMH_SessionEventP
{
/**
* Type is #TALER_DBEVENT_MERCHANT_SESSION_CAPTURED
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Always zero (for alignment).
*/
uint32_t reserved GNUNET_PACKED;
/**
* Merchant's public key
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Hash of the fulfillment URL.
*/
struct GNUNET_HashCode h_fulfillment_url;
/**
* Hash of the session ID
*/
struct GNUNET_HashCode h_session_id;
};
/**
* Event triggered when an order's refund is increased
* or obtained by the respective wallet.
*
* Extra arguments are the amount (as a string).
*/
struct TMH_OrderRefundEventP
{
/**
* Type is #TALER_DBEVENT_MERCHANT_ORDER_REFUND or
* #TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Always zero (for alignment).
*/
uint32_t reserved GNUNET_PACKED;
/**
* Merchant's public key
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Hash of the order ID.
*/
struct GNUNET_HashCode h_order_id;
};
/**
* Event generated when a client picks up a reward.
*/
struct TMH_RewardPickupEventP
{
/**
* Type is #TALER_DBEVENT_MERCHANT_REWARD_PICKUP.
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Always zero (for alignment).
*/
uint32_t reserved GNUNET_PACKED;
/**
* Reward ID.
*/
struct TALER_RewardIdentifierP reward_id;
/**
* Hash of the instance ID.
*/
struct GNUNET_HashCode h_instance;
};
/**
* Possible flags indicating the state of an order.
*/
enum TMH_OrderStateFlags
{
TMH_OSF_NONE = 0,
/**
* Not yet used.
*/
TMH_OSF_CLAIMED = 1,
/**
* Customer paid the order.
*/
TMH_OSF_PAID = 2,
/**
* Merchant granted (possibly partial) refund.
*/
TMH_OSF_REFUNDED = 4,
/**
* Merchant received the payment from the exchange.
* FIXME: not triggered yet!
*/
TMH_OSF_WIRED = 8
};
/**
* Extra information passed for a
* #TALER_DBEVENT_MERCHANT_ORDERS_CHANGE.
*/
struct TMH_OrderChangeEventDetailsP
{
/**
* Order ID, in NBO.
*/
uint64_t order_serial_id GNUNET_PACKED;
/**
* Execution date of the order.
*/
struct GNUNET_TIME_TimestampNBO execution_date;
/**
* See `enum TMH_OrderStateFlags`. In NBO.
*/
uint32_t order_state GNUNET_PACKED;
};
/**
* Event triggered when an order's refund is increased
* or obtained by the respective wallet.
*
* Extra arguments are the amount (as a string).
*/
struct TMH_OrderChangeEventP
{
/**
* Type is #TALER_DBEVENT_MERCHANT_ORDERS_CHANGE.
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Always zero (for alignment).
*/
uint32_t reserved GNUNET_PACKED;
/**
* Merchant's public key
*/
struct TALER_MerchantPublicKeyP merchant_pub;
};
GNUNET_NETWORK_STRUCT_END
/**
* @brief Struct describing an URL and the handler for it.
*
* The overall URL is always @e url_prefix, optionally followed by the
* id_segment, which is optionally followed by the @e url_suffix. It is NOT
* allowed for the @e url_prefix to be directly followed by the @e url_suffix.
* A @e url_suffix SHOULD only be used with a @e method of #MHD_HTTP_METHOD_POST.
*/
struct TMH_RequestHandler;
/**
* This information is stored in the "connection_cls" of MHD for
* every request that we process.
* Individual handlers can evaluate its members and
* are allowed to update @e cc and @e ctx to store and clean up
* handler-specific data.
*/
struct TMH_HandlerContext;
/**
* Possible authorization scopes. This is a bit mask.
*/
enum TMH_AuthScope
{
/**
* Nothing is authorized.
*/
TMH_AS_NONE = 0,
/**
* Read-only access is OK. Any GET request is
* automatically OK.
*/
TMH_AS_READ_ONLY = 1,
/**
* /login access to renew the token is OK.
*/
TMH_AS_REFRESHABLE = 2,
/**
* Full access is granted to everything.
*/
TMH_AS_ALL = 7
};
/**
* @brief Struct describing an URL and the handler for it.
*
* The overall URL is always @e url_prefix, optionally followed by the
* id_segment, which is optionally followed by the @e url_suffix. It is NOT
* allowed for the @e url_prefix to be directly followed by the @e url_suffix.
* A @e url_suffix SHOULD only be used with a @e method of #MHD_HTTP_METHOD_POST.
*/
struct TMH_RequestHandler
{
/**
* URL prefix the handler is for, includes the '/',
* so "/orders", "/templates", "/webhooks" or "/products". Does *not* include
* "/private", that is controlled by the array in which
* the handler is defined. Must not contain any
* '/' except for the leading '/'.
*/
const char *url_prefix;
/**
* Required authentication scope for this request. NONE implies that
* #TMH_AS_ALL is required unless this is a #MHD_HTTP_METHOD_GET method, in which
* case #TMH_AS_READ_ONLY is sufficient.
*/
enum TMH_AuthScope auth_scope;
/**
* Does this request include an identifier segment
* (product_id, reserve_pub, order_id, reward_id, template_id, webhook_id) in the
* second segment?
*/
bool have_id_segment;
/**
* Does this request handler work without an instance?
*/
bool skip_instance;
/**
* Does this endpoint ONLY apply for the default instance?
*/
bool default_only;
/**
* Does this request handler work with a deleted instance?
*/
bool allow_deleted_instance;
/**
* URL suffix the handler is for, excludes the '/',
* so "pay" or "claim", not "/pay".
*/
const char *url_suffix;
/**
* HTTP method the handler is for, NULL for "all".
*/
const char *method;
/**
* Mime type to use in reply (hint, can be NULL).
*/
const char *mime_type;
/**
* Raw data for the @e handler (can be NULL).
*/
const void *data;
/**
* Number of bytes in @e data.
*/
size_t data_size;
/**
* Maximum upload size allowed for this handler.
* 0 for #DEFAULT_MAX_UPLOAD_SIZE.
*/
size_t max_upload;
/**
* Handler to be called for this URL/METHOD combination.
*
* @param rh this struct
* @param connection the MHD connection to handle
* @param[in,out] hc context with further information about the request
* @return MHD result code
*/
MHD_RESULT
(*handler)(const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc);
/**
* Default response code to use.
*/
unsigned int response_code;
};
/**
* Signature of a function used to clean up the context
* we keep in the "connection_cls" of MHD when handling
* a request.
*
* @param ctx the context to clean up.
*/
typedef void
(*TMH_ContextCleanup)(void *ctx);
/**
* This information is stored in the "connection_cls" of MHD for
* every request that we process.
* Individual handlers can evaluate its members and
* are allowed to update @e cc and @e ctx to store and clean up
* handler-specific data.
*/
struct TMH_HandlerContext
{
/**
* Function to execute the handler-specific cleanup of the
* (request-specific) context in @e ctx.
*/
TMH_ContextCleanup cc;
/**
* Client-specific context we keep. Passed to @e cc.
*/
void *ctx;
/**
* Which request handler is handling this request?
*/
const struct TMH_RequestHandler *rh;
/**
* Which instance is handling this request?
*/
struct TMH_MerchantInstance *instance;
/**
* Asynchronous request context id.
*/
struct GNUNET_AsyncScopeId async_scope_id;
/**
* Our original URL, for logging.
*/
const char *url;
/**
* Copy of our original full URL with query parameters.
*/
char *full_url;
/**
* Client-provided authentication token for this
* request, can be NULL.
*
* Used to check for concurrent, conflicting updates of
* the authentication information in the database.
*/
const char *auth_token;
/**
* Infix part of @a url.
*/
char *infix;
/**
* JSON body that was uploaded, NULL if @e has_body is false.
*/
json_t *request_body;
/**
* Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
* Used when we parse the POSTed data.
*/
void *json_parse_context;
/**
* Total size of the upload so far.
*/
uint64_t total_upload;
/**
* Actual authentication scope of this request.
* Only set for ``/private/`` requests.
*/
enum TMH_AuthScope auth_scope;
/**
* Set to true if this is an #MHD_HTTP_METHOD_POST or #MHD_HTTP_METHOD_PATCH request.
* (In principle #MHD_HTTP_METHOD_PUT may also belong, but we do not have PUTs
* in the API today, so we do not test for PUT.)
*/
bool has_body;
};
/**
* Information common for suspended requests.
*/
struct TMH_SuspendedConnection
{
/**
* Which connection was suspended.
*/
struct MHD_Connection *con;
/**
* At what time does this request expire? If set in the future, we
* may wait this long for a payment to arrive before responding.
*/
struct GNUNET_TIME_Absolute long_poll_timeout;
/**
* Minimum refund amount to be exceeded (exclusive this value!) for resume.
*/
struct TALER_Amount refund_expected;
/**
* true if we are waiting for a refund.
*/
bool awaiting_refund;
/**
* Whether we're waiting for the refunds to be obtained.
*/
bool awaiting_refund_obtained;
};
/**
* Which currency do we use?
*/
extern char *TMH_currency;
/**
* What is the base URL for this merchant backend? NULL if it is not
* configured and is to be determined from HTTP headers (X-Forwarded-Host and
* X-Forwarded-Port and X-Forwarded-Prefix) of the reverse proxy.
*/
extern char *TMH_base_url;
/**
* Length of the TMH_cspecs array.
*/
extern unsigned int TMH_num_cspecs;
/**
* Rendering specs for currencies.
*/
extern struct TALER_CurrencySpecification *TMH_cspecs;
/**
* Inform the auditor for all deposit confirmations (global option)
*/
extern int TMH_force_audit;
/**
* Context for all CURL operations (useful to the event loop)
*/
extern struct GNUNET_CURL_Context *TMH_curl_ctx;
/**
* Handle to the database backend.
*/
extern struct TALER_MERCHANTDB_Plugin *TMH_db;
/**
* Hashmap pointing at merchant instances by 'id'. An 'id' is
* just a string that identifies a merchant instance. When a frontend
* needs to specify an instance to the backend, it does so by 'id'
*/
extern struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
/**
* How long do we need to keep information on paid contracts on file for tax
* or other legal reasons? Used to block deletions for younger transaction
* data.
*/
extern struct GNUNET_TIME_Relative TMH_legal_expiration;
/**
* Initial authorization token.
*/
extern char *TMH_default_auth;
/**
* Callback that frees an instances removing
* it from the global hashmap.
*
* @param cls closure, pass NULL
* @param key current key (ignored)
* @param value a `struct TMH_MerchantInstance`
* @return #GNUNET_YES (always)
*/
enum GNUNET_GenericReturnValue
TMH_instance_free_cb (void *cls,
const struct GNUNET_HashCode *key,
void *value);
/**
* Add instance definition to our active set of instances.
*
* @param[in,out] mi merchant instance details to define
* @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
*/
enum GNUNET_GenericReturnValue
TMH_add_instance (struct TMH_MerchantInstance *mi);
/**
* Decrement reference counter of @a mi, and free if it hits zero.
*
* @param[in,out] mi merchant instance to update and possibly free
*/
void
TMH_instance_decref (struct TMH_MerchantInstance *mi);
/**
* Free memory allocated by @a wm.
*
* @param[in] wm wire method to free
*/
void
TMH_wire_method_free (struct TMH_WireMethod *wm);
/**
* Lookup a merchant instance by its instance ID.
*
* @param instance_id identifier of the instance to resolve
* @return NULL if that instance is unknown to us
*/
struct TMH_MerchantInstance *
TMH_lookup_instance (const char *instance_id);
/**
* A transaction modified an instance setting
* (or created/deleted/purged one). Notify all
* backends about the change.
*
* @param id ID of the instance that changed
*/
void
TMH_reload_instances (const char *id);
/**
* Check that @a token hashes to @a hash under @a salt for
* merchant instance authentication.
*
* @param token the token to check
* @param salt the salt to use when hashing
* @param hash the hash to check against
* @return #GNUNET_OK if the @a token matches
*/
enum GNUNET_GenericReturnValue
TMH_check_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
struct TALER_MerchantAuthenticationHashP *hash);
/**
* Compute a @a hash from @a token hashes for
* merchant instance authentication.
*
* @param token the token to check
* @param[out] salt set to a fresh random salt
* @param[out] hash set to the hash of @a token under @a salt
*/
void
TMH_compute_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
struct TALER_MerchantAuthenticationHashP *hash);
#endif