diff options
author | priscilla <priscilla.huang@efrei.net> | 2023-01-12 05:09:09 -0500 |
---|---|---|
committer | priscilla <priscilla.huang@efrei.net> | 2023-01-12 05:09:09 -0500 |
commit | 61dc16d2b4c337a3de93d855fb60b9cc64b5b995 (patch) | |
tree | 023b79198370d39dcb47e71c833d0924b259782c /src | |
parent | f05235f55bddb98989812c35d96504c5b7e2a918 (diff) |
backenddb and backend post pending webhook
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/#taler-merchant-httpd_private-post-webhooks.h# | 43 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-pending-webhooks.c | 207 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-pending-webhooks.h | 43 | ||||
-rw-r--r-- | src/backenddb/merchantdb_helper.c | 10 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 82 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 80 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 31 |
7 files changed, 480 insertions, 16 deletions
diff --git a/src/backend/#taler-merchant-httpd_private-post-webhooks.h# b/src/backend/#taler-merchant-httpd_private-post-webhooks.h# new file mode 100644 index 00000000..fd73c9e7 --- /dev/null +++ b/src/backend/#taler-merchant-httpd_private-post-webhooks.h# @@ -0,0 +1,43 @@ +/* + This file is part of TALER + (C) 2022 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 <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-merchant-httpd_private-post-webhooks.h + * @brief implementing POST /webhooks request handling + * @author Priscilla HUANG + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_WEBHOOKS_H +#define TALER_MERCHANT_HTTPD_PRIVATE_POST_WEBHOOKS_H +#include "taler-merchant-httpd.h" + + +/** + * Generate a webhook entry. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_post_webhooks (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif diff --git a/src/backend/taler-merchant-httpd_private-post-pending-webhooks.c b/src/backend/taler-merchant-httpd_private-post-pending-webhooks.c new file mode 100644 index 00000000..6115124f --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-pending-webhooks.c @@ -0,0 +1,207 @@ +/* + This file is part of TALER + (C) 2023 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 <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-merchant-httpd_private-post-pending-webhooks.c + * @brief implementing POST /pending webhooks request handling + * @author Priscilla HUANG + */ +#include "platform.h" +#include "taler-merchant-httpd_private-post-webhooks.h" +#include "taler-merchant-httpd_helper.h" +#include <taler/taler_json_lib.h> + +/** + * How often do we retry the simple INSERT database transaction? + */ +#define MAX_RETRIES 3 + + +/** + * Check if the two pending webhooks are identical. + * + * @param pw1 pending webhook to compare + * @param pw2 other pending webhook to compare + * @return true if they are 'equal', false if not or of payto_uris is not an array + */ +static bool +pending_webhooks_equal (const struct TALER_MERCHANTDB_PendingWebhookDetails *pw1, + const struct TALER_MERCHANTDB_PendingWebhookDetails *pw2) +{ + return ( (GNUNET_TIME_absolute_cmp (pw1->next_attempt, + ==, + pw2->next_attempt)) && + (pw1->retries == pw2->retries) && + (0 == strcmp (pw1->url, + pw2->url)) && + (0 == strcmp (pw1->http_method, + pw2->http_method)) && + (0 == strcmp (pw1->header, + pw2->heade)) && + (0 == strcmp (pw1->body, + pw2->body))); +} + + +MHD_RESULT +TMH_private_post_pending_webhooks (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + struct TALER_MERCHANTDB_PendingWebhookDetails pwb = { 0 }; + uint64_t *webhook_serial; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("webhook_serial", + &webhook_serial), + GNUNET_JSON_spec_absolute ("next_attempt", + &pwb.next_attempt), + GNUNET_JSON_spec_uint64 ("retries", + &pwb.retries), + GNUNET_JSON_spec_string ("url", + (const char **) &wb.url), + GNUNET_JSON_spec_string ("http_method", + (const char **) &wb.http_method), + GNUNET_JSON_spec_string ("header", + (const char **) &wb.header), + GNUNET_JSON_spec_string ("body", + (const char **) &wb.body), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (NULL != mi); + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + hc->request_body, + spec); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return (GNUNET_NO == res) + ? MHD_YES + : MHD_NO; + } + } + + + + /* finally, interact with DB until no serialization error */ + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + /* Test if a pending webhook of this id is known */ + struct TALER_MERCHANTDB_PendingWebhooksDetails epwb; + + if (GNUNET_OK != + TMH_db->start (TMH_db->cls, + "/post pending webhooks")) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + } + qs = TMH_db->lookup_pending_webhook (TMH_db->cls, + mi->settings.id, + webhook_serial, + &ewb); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + /* Clean up and fail hard */ + GNUNET_break (0); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + case GNUNET_DB_STATUS_SOFT_ERROR: + /* restart transaction */ + goto retry; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Good, we can proceed! */ + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* idempotency check: is ewb == wb? */ + { + bool eq; + + eq = pending_webhooks_equal (&pwb, + &epwb); + TALER_MERCHANTDB_pending_webhook_details_free(&epwb); + TMH_db->rollback (TMH_db->cls); + GNUNET_JSON_parse_free (spec); + return eq + ? TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0) + : TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_PENDING_WEBHOOKS_CONFLICT_PENDING_WEBHOOKS_EXISTS, + webhook_serial + } + } /* end switch (qs) */ + + qs = TMH_db->insert_pending_webhook (TMH_db->cls, + mi->settings.id, + webhook_serial, + &pwb); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + TMH_db->rollback (TMH_db->cls); + break; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + qs = TMH_db->commit (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } +retry: + GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TMH_db->rollback (TMH_db->cls); + } /* for RETRIES loop */ + GNUNET_JSON_parse_free (spec); + if (qs < 0) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + (GNUNET_DB_STATUS_SOFT_ERROR == qs) + ? TALER_EC_GENERIC_DB_SOFT_FAILURE + : TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); + } + return TALER_MHD_reply_static (connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + + /* end of taler-merchant-httpd_private-post-pending-webhooks.c */ diff --git a/src/backend/taler-merchant-httpd_private-post-pending-webhooks.h b/src/backend/taler-merchant-httpd_private-post-pending-webhooks.h new file mode 100644 index 00000000..0bf317b7 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-post-pending-webhooks.h @@ -0,0 +1,43 @@ +/* + This file is part of TALER + (C) 2023 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 <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-merchant-httpd_private-post-pending-webhooks.h + * @brief implementing POST /pending webhooks request handling + * @author Priscilla HUANG + */ +#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_PENDING_WEBHOOKS_H +#define TALER_MERCHANT_HTTPD_PRIVATE_POST_PENDING_WEBHOOKS_H +#include "taler-merchant-httpd.h" + + +/** + * Generate a pending webhook entry. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_private_post_pending_webhooks (const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c index e7d9f459..bef92e8f 100644 --- a/src/backenddb/merchantdb_helper.c +++ b/src/backenddb/merchantdb_helper.c @@ -59,6 +59,16 @@ TALER_MERCHANTDB_webhook_details_free ( GNUNET_free (wb->body_template); } +void +TALER_MERCHANTDB_pending_webhook_details_free ( + struct TALER_MERCHANTDB_PendingWebhookDetails *pwb) +{ + GNUNET_free (pwb->url); + GNUNET_free (pwb->http_method); + GNUNET_free (pwb->header_template); + GNUNET_free (pwb->body_template); +} + /* end of merchantdb_helper.c */ diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index e9ddabe7..22dd188b 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -7513,6 +7513,67 @@ postgres_insert_pending_webhook(void *cls, } /** + * Lookup details about a particular pending webhook. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param webhook_serial webhook to lookup + * @param[out] pwb set to the pending webhook details on success, can be NULL + * (in that case we only want to check if the webhook exists) + * @return database result code + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_pending_webhook (void *cls, + const char *instance_id, + uint64_t *webhook_serial, + struct TALER_MERCHANTDB_PendingWebhookDetails *pwb) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (webhook_serial), + GNUNET_PQ_query_param_end + }; + + if (NULL == pwb) + { + struct GNUNET_PQ_ResultSpec rs_null[] = { + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_webhook", + params, + rs_null); + } + else + { + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_absolute_time ("next_attempt", + &next_attempt), + GNUNET_PQ_result_spec_uint32 ("retries", + &pwb->retries), + GNUNET_PQ_result_spec_string ("url", + &pwb->url), + GNUNET_PQ_result_spec_string ("http_method", + &pwb->http_method), + GNUNET_PQ_result_spec_string ("header", + &pwb->header), + GNUNET_PQ_result_spec_string ("body", + &pwb->body), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_webhook", + params, + rs); + } +} + +/** * Context used for postgres_lookup_future_webhook(). */ struct LookupPendingWebhookContext @@ -7606,9 +7667,9 @@ lookup_pending_webhooks_cb (void *cls, */ // WHERE next_attempt <= now ORDER BY next_attempt ASC static enum GNUNET_DB_QueryStatus -postgres_lookup_pending_webhook(void *cls, - TALER_MERCHANTDB_PendingWebhooksCallback cb, - void *cb_cls) +postgres_lookup_pending_webhooks(void *cls, + TALER_MERCHANTDB_PendingWebhooksCallback cb, + void *cb_cls) { struct PostgresClosure *pg = cls; struct LookupPendingWebhookContext pwlc = { @@ -10380,6 +10441,20 @@ postgres_connect (void *cls) /* for postgres_lookup_pending_webhook() */ GNUNET_PQ_make_prepare ("lookup_pending_webhook", "SELECT" + " next_attempt" + ",retries" + ",url" + ",http_method" + ",header" + ",body" + " FROM merchant_webhook" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1" + " AND merchant_pending_webhook.webhook_serial=$2"), + /* for postgres_lookup_pending_webhook() */ + GNUNET_PQ_make_prepare ("lookup_pending_webhook", + "SELECT" " webhook_serial" ",next_attempt" ",retries" @@ -10580,6 +10655,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->delete_webhook = &postgres_delete_webhook; plugin->insert_webhook = &postgres_insert_webhook; plugin->update_webhook = &postgres_update_webhook; + plugin->lookup_pending_webhook = &postgres_lookup_pending_webhook; plugin->lookup_webhook_by_event = &postgres_lookup_webhook_by_event; plugin->lookup_all_webhooks = &postgres_lookup_all_webhooks; plugin->lookup_future_webhook = &postgres_lookup_future_webhook; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index e24e4c04..cd2f2b16 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -6970,7 +6970,7 @@ test_lookup_template (const struct InstanceData *instance, instance->instance.id, template->id, &lookup_result)) - { + { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup template failed\n"); TALER_MERCHANTDB_template_details_free (&lookup_result); @@ -7833,6 +7833,34 @@ make_pending_webhook (uint64_t webhook_serial, pwebhook->pwebhook.body = "$Amount"; } +/** + * Compare two pending webhooks for equality. + * + * @param a the first pending webhook. + * @param b the second pending webhook. + * @return 0 on equality, 1 otherwise. + */ +static int +check_pending_webhooks_equal (const struct TALER_MERCHANTDB_PendingWebhookDetails *a, + const struct TALER_MERCHANTDB_PendingWebhookDetails *b) +{ + if ((GNUNET_TIME_absolute_cmp (a->next_attempt, + !=, + b->next_attempt)) && + (a->retries != b->retries) && + (0 != strcmp (a->event_type, + b->event_type)) || + (0 != strcmp (a->url, + b->url)) || + (0 != strcmp (a->http_method, + b->http_method)) || + (0 != strcmp (a->header, + b->header)) || + (0 != strcmp (a->body, + b->body))) + return 1; + return 0; +} /** @@ -7885,6 +7913,42 @@ test_update_pending_webhook (const struct InstanceData *instance, /** + * Tests looking up a pending webhook from the db. + * + * @param instance the instance to query from. + * @param pwebhook the pending webhook to query and compare to. + * @return 0 when successful, 1 otherwise. + */ +static int +test_lookup_pending_webhook (const struct InstanceData *instance, + const struct PendingWebhookData *pwebhook) +{ + struct TALER_MERCHANTDB_PendingWebhookDetails lookup_result; + if (0 > plugin->lookup_pending_webhook (plugin->cls, + instance->instance.id, + pwebhook->pwebhook_serial, + &lookup_result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Lookup pending webhook failed\n"); + TALER_MERCHANTDB_webhook_details_free (&lookup_result); + return 1; + } + const struct TALER_MERCHANTDB_PendingWebhookDetails *to_cmp = &pwebhook->pwebhook; + if (0 != check_pending_webhooks_equal (&lookup_result, + to_cmp)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Lookup pending webhook failed: incorrect pending webhook returned\n"); + TALER_MERCHANTDB_pending_webhook_details_free (&lookup_result); + return 1; + } + TALER_MERCHANTDB_pending_webhook_details_free (&lookup_result); + return 0; +} + +/** * Closure for testing pending webhook lookup */ struct TestLookupPendingWebhooks_Closure @@ -7951,7 +8015,7 @@ lookup_pending_webhooks_cb (void *cls, * @return 0 when successful, 1 otherwise. */ static int -test_lookup_pending_webhook (const struct InstanceData *instance, +test_lookup_pending_webhooks (const struct InstanceData *instance, unsigned int pwebhooks_length, const struct PendingWebhookData *pwebhooks) { @@ -8185,6 +8249,9 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, &cls->pwebhooks[0], GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + /* Test lookup of individual pending webhook */ + TEST_RET_ON_FAIL (test_lookup_pending_webhook (&cls->instance, + &cls->pwebhooks[0])); /* Test pending webhook update */ cls->pwebhooks[0].pwebhook.next_attempt = GNUNET_TIME_absolute_get (); @@ -8194,6 +8261,9 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, &cls->pwebhooks[1], GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); + /* Test lookup of individual pending webhook */ + TEST_RET_ON_FAIL (test_lookup_pending_webhook (&cls->instance, + &cls->pwebhooks[0])); /* Test collective pending webhook lookup */ TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, @@ -8202,9 +8272,9 @@ run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance, 1, cls->pwebhooks)); - TEST_RET_ON_FAIL (test_lookup_pending_webhook (&cls->instance, - 2, - cls->pwebhooks)); + TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance, + 2, + cls->pwebhooks)); TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 2, cls->pwebhooks)); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index c2823d92..2ae91046 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -434,17 +434,17 @@ typedef void struct TALER_MERCHANTDB_PendingWebhookDetails { /** - * How often have we tried this request so far. - */ - uint32_t retries; - - /** * Identifies when we should make the next request to the webhook. 0 for unknown, * #GNUNET_TIME_UNIT_FOREVER_ABS for never. */ struct GNUNET_TIME_Absolute next_attempt; /** + * How often have we tried this request so far. + */ + uint32_t retries; + + /** * URL of the webhook. The customer will be redirected on this url. */ char *url; @@ -2756,6 +2756,21 @@ struct TALER_MERCHANTDB_Plugin const char *body); /** + * Lookup details about a particular pending webhook. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param webhook_serial webhook to lookup + * @param[out] pwb set to the pending webhook details on success, can be NULL + * (in that case we only want to check if the webhook exists) + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*lookup_pending_webhook) (void *cls, + const char *instance_id, + uint64_t *webhook_serial, + struct TALER_MERCHANTDB_PendingWebhookDetails *pwb); + /** * Lookup the webhook that need to be send in priority. These webhooks are not successfully * send. * @@ -2765,9 +2780,9 @@ struct TALER_MERCHANTDB_Plugin */ // WHERE next_attempt <= now ORDER BY next_attempt ASC enum GNUNET_DB_QueryStatus - (*lookup_pending_webhook)(void *cls, - TALER_MERCHANTDB_PendingWebhooksCallback cb, - void *cb_cls); + (*lookup_pending_webhooks)(void *cls, + TALER_MERCHANTDB_PendingWebhooksCallback cb, + void *cb_cls); /** * Lookup future webhook in the pending webhook that need to be send. |