summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd.c')
-rw-r--r--src/backend/taler-merchant-httpd.c739
1 files changed, 521 insertions, 218 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 8e1a0fc0..7384bfc9 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 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
@@ -28,23 +28,23 @@
#include <taler/taler_mhd_lib.h>
#include <taler/taler_templating_lib.h>
#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_config.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_get-orders-ID.h"
-#include "taler-merchant-httpd_get-tips-ID.h"
+#include "taler-merchant-httpd_get-templates-ID.h"
#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
-#include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-account-ID.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
+#include "taler-merchant-httpd_private-delete-instances-ID-token.h"
#include "taler-merchant-httpd_private-delete-products-ID.h"
#include "taler-merchant-httpd_private-delete-orders-ID.h"
-#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
+#include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
#include "taler-merchant-httpd_private-delete-transfers-ID.h"
-#include "taler-merchant-httpd_private-get-webhooks.h"
-#include "taler-merchant-httpd_private-get-webhooks-ID.h"
-#include "taler-merchant-httpd_private-get-templates.h"
-#include "taler-merchant-httpd_private-get-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
+#include "taler-merchant-httpd_private-get-accounts.h"
+#include "taler-merchant-httpd_private-get-accounts-ID.h"
#include "taler-merchant-httpd_private-get-instances.h"
#include "taler-merchant-httpd_private-get-instances-ID.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
@@ -52,35 +52,42 @@
#include "taler-merchant-httpd_private-get-products-ID.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler-merchant-httpd_private-get-orders-ID.h"
-#include "taler-merchant-httpd_private-get-reserves.h"
-#include "taler-merchant-httpd_private-get-reserves-ID.h"
-#include "taler-merchant-httpd_private-get-tips-ID.h"
-#include "taler-merchant-httpd_private-get-tips.h"
+#include "taler-merchant-httpd_private-get-otp-devices.h"
+#include "taler-merchant-httpd_private-get-otp-devices-ID.h"
+#include "taler-merchant-httpd_private-get-templates.h"
+#include "taler-merchant-httpd_private-get-templates-ID.h"
+#include "taler-merchant-httpd_private-get-token-families.h"
+#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
#include "taler-merchant-httpd_private-get-transfers.h"
-#include "taler-merchant-httpd_private-patch-webhooks-ID.h"
-#include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_private-get-webhooks.h"
+#include "taler-merchant-httpd_private-get-webhooks-ID.h"
+#include "taler-merchant-httpd_private-patch-accounts-ID.h"
#include "taler-merchant-httpd_private-patch-instances-ID.h"
#include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
+#include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
#include "taler-merchant-httpd_private-patch-products-ID.h"
-#include "taler-merchant-httpd_private-post-webhooks.h"
-#include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
+#include "taler-merchant-httpd_private-patch-webhooks-ID.h"
+#include "taler-merchant-httpd_private-post-account.h"
#include "taler-merchant-httpd_private-post-instances.h"
#include "taler-merchant-httpd_private-post-instances-ID-auth.h"
+#include "taler-merchant-httpd_private-post-instances-ID-token.h"
+#include "taler-merchant-httpd_private-post-otp-devices.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
#include "taler-merchant-httpd_private-post-products.h"
#include "taler-merchant-httpd_private-post-products-ID-lock.h"
-#include "taler-merchant-httpd_private-post-reserves.h"
-#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
+#include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_private-post-token-families.h"
#include "taler-merchant-httpd_private-post-transfers.h"
+#include "taler-merchant-httpd_private-post-webhooks.h"
#include "taler-merchant-httpd_post-orders-ID-abort.h"
#include "taler-merchant-httpd_post-orders-ID-claim.h"
#include "taler-merchant-httpd_post-orders-ID-paid.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_post-using-templates.h"
#include "taler-merchant-httpd_post-orders-ID-refund.h"
-#include "taler-merchant-httpd_post-tips-ID-pickup.h"
-#include "taler-merchant-httpd_reserves.h"
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
@@ -107,6 +114,13 @@
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.
+ */
+char *TMH_base_url;
+
+/**
* Inform the auditor for all deposit confirmations (global option)
*/
int TMH_force_audit;
@@ -136,6 +150,16 @@ struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
struct GNUNET_TIME_Relative TMH_legal_expiration;
/**
+ * Length of the TMH_cspecs array.
+ */
+unsigned int TMH_num_cspecs;
+
+/**
+ * Rendering specs for currencies.
+ */
+struct TALER_CurrencySpecification *TMH_cspecs;
+
+/**
* The port we are running on
*/
static uint16_t port;
@@ -146,9 +170,20 @@ static uint16_t port;
static int merchant_connection_close;
/**
+ * Context for all exchange operations (useful to the event loop).
+ */
+struct GNUNET_CURL_Context *TMH_curl_ctx;
+
+/**
+ * Context for integrating #TMH_curl_ctx with the
+ * GNUnet event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc;
+
+/**
* Global return code
*/
-static int result;
+static int global_ret;
/**
* Our configuration.
@@ -161,6 +196,75 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg;
char *TMH_default_auth;
+/**
+ * Check validity of login @a token for the given @a instance_id.
+ *
+ * @param token the login token given in the request
+ * @param instance_id the instance the login is to be checked against
+ * @param[out] as set to scope of the token if it is valid
+ * @return TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+TMH_check_token (const char *token,
+ const char *instance_id,
+ enum TMH_AuthScope *as)
+{
+ enum TMH_AuthScope scope;
+ struct GNUNET_TIME_Timestamp expiration;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MERCHANTDB_LoginTokenP btoken;
+
+ if (NULL == token)
+ {
+ *as = TMH_AS_NONE;
+ return TALER_EC_NONE;
+ }
+ /* This was presumably checked before... */
+ GNUNET_assert (0 == strncasecmp (token,
+ RFC_8959_PREFIX,
+ strlen (RFC_8959_PREFIX)));
+ token += strlen (RFC_8959_PREFIX);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (token,
+ strlen (token),
+ &btoken,
+ sizeof (btoken)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Given authorization token `%s' is malformed\n",
+ token);
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_TOKEN_MALFORMED;
+ }
+ qs = TMH_db->select_login_token (TMH_db->cls,
+ instance_id,
+ &btoken,
+ &expiration,
+ &scope);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_EC_GENERIC_DB_FETCH_FAILED;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Authorization token `%s' unknown\n",
+ token);
+ return TALER_EC_GENERIC_TOKEN_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Authorization token `%s' expired\n",
+ token);
+ return TALER_EC_GENERIC_TOKEN_EXPIRED;
+ }
+ *as = scope;
+ return TALER_EC_NONE;
+}
+
+
enum GNUNET_GenericReturnValue
TMH_check_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
@@ -225,6 +329,17 @@ TMH_compute_auth (const char *token,
void
+TMH_wire_method_free (struct TMH_WireMethod *wm)
+{
+ GNUNET_free (wm->payto_uri);
+ GNUNET_free (wm->wire_method);
+ GNUNET_free (wm->credit_facade_url);
+ json_decref (wm->credit_facade_credentials);
+ GNUNET_free (wm);
+}
+
+
+void
TMH_instance_decref (struct TMH_MerchantInstance *mi)
{
struct TMH_WireMethod *wm;
@@ -238,9 +353,7 @@ TMH_instance_decref (struct TMH_MerchantInstance *mi)
GNUNET_CONTAINER_DLL_remove (mi->wm_head,
mi->wm_tail,
wm);
- GNUNET_free (wm->payto_uri);
- GNUNET_free (wm->wire_method);
- GNUNET_free (wm);
+ TMH_wire_method_free (wm);
}
GNUNET_free (mi->settings.id);
@@ -282,13 +395,11 @@ static void
do_shutdown (void *cls)
{
(void) cls;
+ TMH_force_orders_resume ();
TMH_force_ac_resume ();
TMH_force_pc_resume ();
TMH_force_kyc_resume ();
- TMH_force_rc_resume ();
TMH_force_gorc_resume ();
- TMH_force_post_transfers_resume ();
- TMH_force_tip_pickup_resume ();
TMH_force_wallet_get_order_resume ();
TMH_force_wallet_refund_order_resume ();
{
@@ -298,19 +409,17 @@ do_shutdown (void *cls)
if (NULL != mhd)
MHD_stop_daemon (mhd);
}
- TMH_RESERVES_done ();
if (NULL != instance_eh)
{
TMH_db->event_listen_cancel (instance_eh);
instance_eh = NULL;
}
+ TMH_EXCHANGES_done ();
if (NULL != TMH_db)
{
TALER_MERCHANTDB_plugin_unload (TMH_db);
TMH_db = NULL;
}
- TMH_EXCHANGES_done ();
- TMH_AUDITORS_done ();
if (NULL != TMH_by_id_map)
{
GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
@@ -320,6 +429,16 @@ do_shutdown (void *cls)
TMH_by_id_map = NULL;
}
TALER_TEMPLATING_done ();
+ if (NULL != TMH_curl_ctx)
+ {
+ GNUNET_CURL_fini (TMH_curl_ctx);
+ TMH_curl_ctx = NULL;
+ }
+ if (NULL != merchant_curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc);
+ merchant_curl_rc = NULL;
+ }
}
@@ -379,6 +498,7 @@ handle_mhd_completion_callback (void *cls,
json_decref (hc->request_body);
if (NULL != hc->instance)
TMH_instance_decref (hc->instance);
+ GNUNET_free (hc->full_url);
GNUNET_free (hc);
*con_cls = NULL;
}
@@ -459,6 +579,15 @@ handle_server_options (const struct TMH_RequestHandler *rh,
}
+/**
+ * Generates the response for "/", redirecting the
+ * client to the "/webui/" from where we serve the SPA.
+ *
+ * @param rh request handler
+ * @param connection MHD connection
+ * @param hc handler context
+ * @return MHD result code
+ */
static MHD_RESULT
spa_redirect (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -466,6 +595,7 @@ spa_redirect (const struct TMH_RequestHandler *rh,
{
const char *text = "Redirecting to /webui/";
struct MHD_Response *response;
+ char *dst;
response = MHD_create_response_from_buffer (strlen (text),
(void *) text,
@@ -480,15 +610,25 @@ spa_redirect (const struct TMH_RequestHandler *rh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
"text/plain"));
+ if ( (NULL == hc->instance) ||
+ (0 == strcmp ("default",
+ hc->instance->settings.id)) )
+ dst = GNUNET_strdup ("/webui/");
+ else
+ GNUNET_asprintf (&dst,
+ "/instances/%s/webui/",
+ hc->instance->settings.id);
if (MHD_NO ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_LOCATION,
- "/webui/"))
+ dst))
{
GNUNET_break (0);
MHD_destroy_response (response);
+ GNUNET_free (dst);
return MHD_NO;
}
+ GNUNET_free (dst);
{
MHD_RESULT ret;
@@ -515,12 +655,14 @@ extract_token (const char **auth)
const char *bearer = "Bearer ";
const char *tok = *auth;
- if (0 != strncmp (tok, bearer, strlen (bearer)))
+ if (0 != strncmp (tok,
+ bearer,
+ strlen (bearer)))
{
*auth = NULL;
return;
}
- tok = tok + strlen (bearer);
+ tok += strlen (bearer);
while (' ' == *tok)
tok++;
if (0 != strncasecmp (tok,
@@ -596,6 +738,29 @@ prefix_match (const struct TMH_RequestHandler *rh,
/**
+ * Function called first by MHD with the full URL.
+ *
+ * @param cls NULL
+ * @param full_url the full URL
+ * @param con MHD connection object
+ * @return our handler context
+ */
+static void *
+full_url_track_callback (void *cls,
+ const char *full_url,
+ struct MHD_Connection *con)
+{
+ struct TMH_HandlerContext *hc;
+
+ hc = GNUNET_new (struct TMH_HandlerContext);
+ GNUNET_async_scope_fresh (&hc->async_scope_id);
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+ hc->full_url = GNUNET_strdup (full_url);
+ return hc;
+}
+
+
+/**
* A client has requested the given url using the given method
* (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
* #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
@@ -630,7 +795,8 @@ prefix_match (const struct TMH_RequestHandler *rh,
* If necessary, this state can be cleaned up in the
* global #MHD_RequestCompletedCallback (which
* can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
- * Initially, `*con_cls` will be NULL.
+ * Initially, `*con_cls` will be set up by the
+ * full_url_track_callback().
* @return #MHD_YES if the connection was handled successfully,
* #MHD_NO if the socket must be closed due to a serious
* error while handling the request
@@ -711,7 +877,7 @@ url_handler (void *cls,
/* Body should be pretty small. */
.max_upload = 1024 * 1024
},
- /* POST /kyc: */
+ /* GET /kyc: */
{
.url_prefix = "/instances/",
.url_suffix = "kyc",
@@ -880,100 +1046,66 @@ url_handler (void *cls,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_orders_ID
},
- /* POST /reserves: */
+ /* POST /transfers: */
{
- .url_prefix = "/reserves",
+ .url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_reserves,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_post_transfers,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
- /* DELETE /reserves/$ID: */
+ /* DELETE /transfers/$ID: */
{
- .url_prefix = "/reserves/",
- .have_id_segment = true,
- .allow_deleted_instance = true,
+ .url_prefix = "/transfers/",
.method = MHD_HTTP_METHOD_DELETE,
- .handler = &TMH_private_delete_reserves_ID
- },
- /* POST /reserves/$ID/authorize-tip: */
- {
- .url_prefix = "/reserves/",
- .url_suffix = "authorize-tip",
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_delete_transfers_ID,
.have_id_segment = true,
- .method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_reserves_ID_authorize_tip,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
- },
- /* POST /tips: */
- {
- .url_prefix = "/tips",
- .method = MHD_HTTP_METHOD_POST,
- .handler = &TMH_private_post_tips,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
- /* GET /tips: */
- {
- .url_prefix = "/tips",
- .allow_deleted_instance = true,
- .method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_tips
- },
- /* GET /tips/$ID: */
+ /* GET /transfers: */
{
- .url_prefix = "/tips/",
+ .url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
- .have_id_segment = true,
- .handler = &TMH_private_get_tips_ID
+ .handler = &TMH_private_get_transfers
},
- /* GET /reserves: */
+ /* POST /otp-devices: */
{
- .url_prefix = "/reserves",
- .allow_deleted_instance = true,
- .method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves
+ .url_prefix = "/otp-devices",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_otp_devices
},
- /* GET /reserves/$ID: */
+ /* GET /otp-devices: */
{
- .url_prefix = "/reserves/",
- .allow_deleted_instance = true,
- .have_id_segment = true,
+ .url_prefix = "/otp-devices",
.method = MHD_HTTP_METHOD_GET,
- .handler = &TMH_private_get_reserves_ID
+ .handler = &TMH_private_get_otp_devices
},
- /* POST /transfers: */
+ /* GET /otp-devices/$ID/: */
{
- .url_prefix = "/transfers",
- .method = MHD_HTTP_METHOD_POST,
- .allow_deleted_instance = true,
- .handler = &TMH_private_post_transfers,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_otp_devices_ID
},
- /* DELETE /transfers/$ID: */
+ /* DELETE /otp-devices/$ID/: */
{
- .url_prefix = "/transfers/",
+ .url_prefix = "/otp-devices/",
.method = MHD_HTTP_METHOD_DELETE,
- .allow_deleted_instance = true,
- .handler = &TMH_private_delete_transfers_ID,
.have_id_segment = true,
- /* the body should be pretty small, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .handler = &TMH_private_delete_otp_devices_ID
},
- /* GET /transfers: */
+ /* PATCH /otp-devices/$ID/: */
{
- .url_prefix = "/transfers",
- .method = MHD_HTTP_METHOD_GET,
- .allow_deleted_instance = true,
- .handler = &TMH_private_get_transfers
+ .url_prefix = "/otp-devices/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_otp_devices_ID
},
/* POST /templates: */
{
@@ -1067,16 +1199,114 @@ url_handler (void *cls,
in the code... */
.max_upload = 1024 * 1024 * 8
},
+ /* POST /accounts: */
+ {
+ .url_prefix = "/accounts",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_account,
+ /* allow account details of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
+ /* PATCH /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler = &TMH_private_patch_accounts_ID,
+ .have_id_segment = true,
+ /* allow account details of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
+ /* GET /accounts: */
+ {
+ .url_prefix = "/accounts",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_accounts
+ },
+ /* GET /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_accounts_ID
+ },
+ /* DELETE /accounts/$H_WIRE: */
+ {
+ .url_prefix = "/accounts/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_account_ID,
+ .have_id_segment = true
+ },
+ /* POST /token: */
+ {
+ .url_prefix = "/token",
+ .auth_scope = TMH_AS_REFRESHABLE,
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_instances_ID_token,
+ /* Body should be tiny. */
+ .max_upload = 1024
+ },
+ /* DELETE /token: */
+ {
+ .url_prefix = "/token",
+ .auth_scope = TMH_AS_READ_ONLY,
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_instances_ID_token,
+ },
+ /* GET /tokenfamilies: */
+ {
+ .url_prefix = "/tokenfamilies",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_tokenfamilies
+ },
+ /* POST /tokenfamilies: */
+ {
+ .url_prefix = "/tokenfamilies",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_token_families
+ },
+ /* GET /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .handler = &TMH_private_get_tokenfamilies_SLUG
+ },
+ /* DELETE /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
+ .handler = &TMH_private_delete_token_families_SLUG
+ },
+ /* PATCH /tokenfamilies/$SLUG/: */
+ {
+ .url_prefix = "/tokenfamilies/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_token_family_SLUG,
+ },
{
.url_prefix = NULL
}
};
static struct TMH_RequestHandler public_handlers[] = {
{
+ /* for "default" instance, it does not even
+ have to exist before we give the WebUI */
.url_prefix = "/",
.method = MHD_HTTP_METHOD_GET,
.mime_type = "text/html",
.skip_instance = true,
+ .default_only = true,
+ .handler = &spa_redirect,
+ .response_code = MHD_HTTP_FOUND
+ },
+ {
+ /* for "normal" instance,s they must exist
+ before we give the WebUI */
+ .url_prefix = "/",
+ .method = MHD_HTTP_METHOD_GET,
+ .mime_type = "text/html",
.handler = &spa_redirect,
.response_code = MHD_HTTP_FOUND
},
@@ -1175,39 +1405,25 @@ url_handler (void *cls,
.have_id_segment = true,
.handler = &TMH_get_orders_ID
},
- /* GET /tips/$ID: */
+ /* GET /static/ *: */
{
- .url_prefix = "/tips/",
+ .url_prefix = "/static/",
.method = MHD_HTTP_METHOD_GET,
- .allow_deleted_instance = true,
- .have_id_segment = true,
- .handler = &TMH_get_tips_ID
- },
- /* POST /tips/$ID/pickup: */
- {
- .url_prefix = "/tips/",
- .method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
- .allow_deleted_instance = true,
- .url_suffix = "pickup",
- .handler = &TMH_post_tips_ID_pickup,
- /* wallet may give us many coins to sign, allow 1 MB of upload
- to set a conservative bound for sane wallets */
- .max_upload = 1024 * 1024
+ .handler = &TMH_return_static
},
- /* GET /static/ *: */
+ /* GET /templates/$ID/: */
{
- .url_prefix = "/static/",
+ .url_prefix = "/templates/",
.method = MHD_HTTP_METHOD_GET,
.have_id_segment = true,
- .handler = &TMH_return_static
+ .handler = &TMH_get_templates_ID
},
/* POST /templates/$ID: */
{
.url_prefix = "/templates/",
.method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
- .allow_deleted_instance = true,
.handler = &TMH_post_using_templates_ID,
.max_upload = 1024 * 1024
},
@@ -1226,7 +1442,7 @@ url_handler (void *cls,
(void) cls;
(void) version;
- if (NULL != hc)
+ if (NULL != hc->url)
{
/* MHD calls us again for a request, for first call
see 'else' case below */
@@ -1274,10 +1490,6 @@ url_handler (void *cls,
connection,
hc);
}
- hc = GNUNET_new (struct TMH_HandlerContext);
- *con_cls = hc;
- GNUNET_async_scope_fresh (&hc->async_scope_id);
- GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
hc->url = url;
{
const char *correlation_id;
@@ -1331,6 +1543,37 @@ url_handler (void *cls,
else
instance_id = GNUNET_strndup (istart,
slash - istart);
+ if (0 == strcmp (instance_id,
+ "default"))
+ {
+ MHD_RESULT ret;
+ struct MHD_Response *response;
+ const char *rstart = hc->full_url + strlen (instance_prefix);
+ const char *rslash = strchr (rstart, '/');
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client used deprecated '/instances/default/' path. Redirecting to modern path\n");
+
+ response
+ = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (response);
+ if (MHD_NO ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ rslash))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (response);
+ return MHD_NO;
+ }
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_PERMANENT_REDIRECT,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+ }
hc->instance = TMH_lookup_instance (instance_id);
if ( (NULL == hc->instance) &&
(0 == strcmp ("default",
@@ -1399,7 +1642,11 @@ url_handler (void *cls,
"/private")) )
{
handlers = private_handlers;
- url += strlen (private_prefix) - 1;
+ if (0 == strcmp (url,
+ "/private"))
+ url = "/";
+ else
+ url += strlen (private_prefix) - 1;
}
else
{
@@ -1566,8 +1813,8 @@ url_handler (void *cls,
(! hc->rh->skip_instance) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Instance `%s' not known\n",
- hc->infix);
+ "Instance for `%s' not known\n",
+ hc->url);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
@@ -1619,9 +1866,40 @@ url_handler (void *cls,
(! auth_malformed) &&
(0 == strcmp (auth,
TMH_default_auth)) );
- if (! auth_ok)
+ if (auth_ok)
{
- if (auth_malformed)
+ hc->auth_scope = TMH_AS_ALL;
+ }
+ else
+ {
+ if (NULL != hc->instance)
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = TMH_check_token (auth,
+ hc->instance->settings.id,
+ &hc->auth_scope);
+ if (TALER_EC_NONE != ec)
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ else
+ hc->auth_scope = TMH_AS_NONE;
+ }
+ /* We grant access if:
+ - scope is 'all'
+ - rh has an explicit non-NONE scope that matches
+ - scope is 'read only' and we have a GET request */
+ if (! ( (TMH_AS_ALL == hc->auth_scope) ||
+ ( (TMH_AS_NONE != hc->rh->auth_scope) &&
+ (hc->rh->auth_scope == (hc->rh->auth_scope & hc->auth_scope)) ) ||
+ ( (TMH_AS_READ_ONLY == (hc->auth_scope & TMH_AS_READ_ONLY)) &&
+ (0 == strcmp (MHD_HTTP_METHOD_GET,
+ method)) ) ) )
+ {
+ if (auth_malformed &&
+ (TMH_AS_NONE == hc->auth_scope) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_UNAUTHORIZED,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
@@ -1667,39 +1945,10 @@ url_handler (void *cls,
MHD_HTTP_METHOD_PATCH)) );
if (hc->has_body)
{
- const char *cl;
-
- /* Maybe check for maximum upload size
- and refuse requests if they are just too big. */
- cl = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONTENT_LENGTH);
- if (NULL != cl)
- {
- unsigned long long cv;
- size_t mul = hc->rh->max_upload;
- char dummy;
-
- if (0 == mul)
- mul = DEFAULT_MAX_UPLOAD_SIZE;
- if (1 != sscanf (cl,
- "%llu%c",
- &cv,
- &dummy))
- {
- /* Not valid HTTP request, just close connection. */
- GNUNET_break_op (0);
- return MHD_NO;
- }
- if (cv > mul)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PAYLOAD_TOO_LARGE,
- TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
- cl);
- }
- }
+ TALER_MHD_check_content_length (connection,
+ 0 == hc->rh->max_upload
+ ? DEFAULT_MAX_UPLOAD_SIZE
+ : hc->rh->max_upload);
GNUNET_break (NULL == hc->request_body); /* can't have it already */
}
return MHD_YES; /* wait for MHD to call us again */
@@ -1707,6 +1956,31 @@ url_handler (void *cls,
/**
+ * Callback invoked with information about a bank account.
+ *
+ * @param cls closure with a `struct TMH_MerchantInstance *`
+ * @param acc details about the account
+ */
+static void
+add_account_cb (void *cls,
+ const struct TALER_MERCHANTDB_AccountDetails *acc)
+{
+ struct TMH_MerchantInstance *mi = cls;
+ struct TMH_WireMethod *wm;
+
+ wm = GNUNET_new (struct TMH_WireMethod);
+ wm->h_wire = acc->h_wire;
+ wm->payto_uri = GNUNET_strdup (acc->payto_uri);
+ wm->wire_salt = acc->salt;
+ wm->wire_method = TALER_payto_get_method (acc->payto_uri);
+ wm->active = acc->active;
+ GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+ mi->wm_tail,
+ wm);
+}
+
+
+/**
* Function called during startup to add all known instances to our
* hash map in memory for faster lookups when we receive requests.
*
@@ -1715,19 +1989,16 @@ url_handler (void *cls,
* @param merchant_priv private key of the instance, NULL if not available
* @param is detailed configuration settings for the instance
* @param ias authentication settings for the instance
- * @param accounts_length length of the @a accounts array
- * @param accounts list of accounts of the merchant
*/
static void
add_instance_cb (void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
- unsigned int accounts_length,
- const struct TALER_MERCHANTDB_AccountDetails accounts[])
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct TMH_MerchantInstance *mi;
+ enum GNUNET_DB_QueryStatus qs;
(void) cls;
mi = TMH_lookup_instance (is->id);
@@ -1757,20 +2028,15 @@ add_instance_cb (void *cls,
else
mi->deleted = true;
mi->merchant_pub = *merchant_pub;
- for (unsigned int i = 0; i<accounts_length; i++)
+ qs = TMH_db->select_accounts (TMH_db->cls,
+ mi->settings.id,
+ &add_account_cb,
+ mi);
+ if (0 > qs)
{
- const struct TALER_MERCHANTDB_AccountDetails *acc = &accounts[i];
- struct TMH_WireMethod *wm;
-
- wm = GNUNET_new (struct TMH_WireMethod);
- wm->h_wire = acc->h_wire;
- wm->payto_uri = GNUNET_strdup (acc->payto_uri);
- wm->wire_salt = acc->salt;
- wm->wire_method = TALER_payto_get_method (acc->payto_uri);
- wm->active = acc->active;
- GNUNET_CONTAINER_DLL_insert (mi->wm_head,
- mi->wm_tail,
- wm);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error loading accounts of `%s' from database\n",
+ mi->settings.id);
}
GNUNET_assert (GNUNET_OK ==
TMH_add_instance (mi));
@@ -1794,8 +2060,6 @@ load_instances (void *cls,
const char *id = extra;
(void) cls;
- (void) extra;
- (void) extra_len;
if ( (NULL != extra) &&
( (0 == extra_len) ||
('\0' != id[extra_len - 1]) ) )
@@ -1837,7 +2101,7 @@ load_instances (void *cls,
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed initialization. Check database setup.\n");
- result = EXIT_FAILURE;
+ global_ret = EXIT_NOPERMISSION;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1845,9 +2109,8 @@ load_instances (void *cls,
/**
- * A transaction modified an instance setting
- * (or created/deleted/purged one). Notify all
- * backends about the change.
+ * 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
*/
@@ -1889,7 +2152,6 @@ run (void *cls,
int fh;
enum TALER_MHD_GlobalOptions go;
int elen;
- int alen;
const char *tok;
(void) cls;
@@ -1904,10 +2166,10 @@ run (void *cls,
RFC_8959_PREFIX,
strlen (RFC_8959_PREFIX))) )
{
- fprintf (stderr,
- "Authentication token does not start with `%s' prefix\n",
- RFC_8959_PREFIX);
- result = GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication token does not start with `%s' prefix\n",
+ RFC_8959_PREFIX);
+ global_ret = EXIT_NOTCONFIGURED;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1919,16 +2181,46 @@ run (void *cls,
go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
TALER_MHD_setup (go);
- result = GNUNET_SYSERR;
+ global_ret = EXIT_SUCCESS;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
- if (GNUNET_OK !=
+
+ TMH_curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &merchant_curl_rc);
+ if (NULL == TMH_curl_ctx)
+ {
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx);
+ /* Disable 100 continue processing */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_CURL_append_header (TMH_curl_ctx,
+ MHD_HTTP_HEADER_EXPECT ":"));
+ GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx,
+ "Taler-Correlation-Id");
+
+ if (GNUNET_SYSERR ==
TALER_config_get_currency (cfg,
&TMH_currency))
{
+
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_CONFIG_parse_currencies (cfg,
+ &TMH_num_cspecs,
+ &TMH_cspecs))
+ {
GNUNET_SCHEDULER_shutdown ();
return;
}
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"merchant",
@@ -1941,6 +2233,22 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant",
+ "BASE_URL",
+ &TMH_base_url))
+ {
+ if (! TALER_is_web_url (TMH_base_url))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "merchant",
+ "BASE_URL",
+ "Needs to start with 'http://' or 'https://'");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (cfg,
"merchant",
@@ -1957,46 +2265,41 @@ run (void *cls,
}
/* /static/ is currently not used */
/* (void) TMH_statics_init (); */
- elen = TMH_EXCHANGES_init (config);
- if (GNUNET_SYSERR == elen)
+ if (NULL ==
+ (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
+ GNUNET_YES)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- alen = TMH_AUDITORS_init (config);
- if (GNUNET_SYSERR == alen)
+ if (NULL ==
+ (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (0 == elen + alen)
+ if (GNUNET_OK !=
+ TMH_db->connect (TMH_db->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fatal: no trusted exchanges and no trusted auditors configured. Exiting.\n");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (NULL ==
- (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
- GNUNET_YES)))
- {
+ "Failed to initialize database connection\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (NULL ==
- (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
+ elen = TMH_EXCHANGES_init (config);
+ if (GNUNET_SYSERR == elen)
{
GNUNET_SCHEDULER_shutdown ();
return;
}
- if (GNUNET_OK !=
- TMH_db->connect (TMH_db->cls))
+ if (0 == elen)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize database connection\n");
+ "Fatal: no trusted exchanges configured. Exiting.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
+
{
struct GNUNET_DB_EventHeaderP es = {
.size = ntohs (sizeof (es)),
@@ -2012,8 +2315,6 @@ run (void *cls,
load_instances (NULL,
NULL,
0);
- /* start watching reserves */
- TMH_RESERVES_init ();
fh = TALER_MHD_bind (cfg,
"merchant",
&port);
@@ -2033,6 +2334,8 @@ run (void *cls,
NULL, NULL,
&url_handler, NULL,
MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_URI_LOG_CALLBACK,
+ &full_url_track_callback, NULL,
MHD_OPTION_NOTIFY_COMPLETED,
&handle_mhd_completion_callback, NULL,
MHD_OPTION_CONNECTION_TIMEOUT,
@@ -2045,7 +2348,7 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
- result = GNUNET_OK;
+ global_ret = EXIT_SUCCESS;
TALER_MHD_daemon_start (mhd);
}
}
@@ -2089,5 +2392,5 @@ main (int argc,
return EXIT_INVALIDARGUMENT;
if (GNUNET_NO == res)
return EXIT_SUCCESS;
- return (GNUNET_OK == result) ? EXIT_SUCCESS : 1;
+ return global_ret;
}