taler-merchant-httpd_private-post-webhooks.c (7290B)
1 /* 2 This file is part of TALER 3 (C) 2022 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file taler-merchant-httpd_private-post-webhooks.c 22 * @brief implementing POST /webhooks request handling 23 * @author Priscilla HUANG 24 */ 25 #include "platform.h" 26 #include "taler-merchant-httpd_private-post-webhooks.h" 27 #include "taler-merchant-httpd_helper.h" 28 #include <taler/taler_json_lib.h> 29 30 31 /** 32 * How often do we retry the simple INSERT database transaction? 33 */ 34 #define MAX_RETRIES 3 35 36 37 /** 38 * Check if the two webhooks are identical. 39 * 40 * @param w1 webhook to compare 41 * @param w2 other webhook to compare 42 * @return true if they are 'equal', false if not or of payto_uris is not an array 43 */ 44 static bool 45 webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *w1, 46 const struct TALER_MERCHANTDB_WebhookDetails *w2) 47 { 48 return ( (0 == strcmp (w1->event_type, 49 w2->event_type)) && 50 (0 == strcmp (w1->url, 51 w2->url)) && 52 (0 == strcmp (w1->http_method, 53 w2->http_method)) && 54 ( ( (NULL == w1->header_template) && 55 (NULL == w2->header_template) ) || 56 ( (NULL != w1->header_template) && 57 (NULL != w2->header_template) && 58 (0 == strcmp (w1->header_template, 59 w2->header_template)) ) ) && 60 ( ( (NULL == w1->body_template) && 61 (NULL == w2->body_template) ) || 62 ( (NULL != w1->body_template) && 63 (NULL != w2->body_template) && 64 (0 == strcmp (w1->body_template, 65 w2->body_template)) ) ) ); 66 } 67 68 69 MHD_RESULT 70 TMH_private_post_webhooks (const struct TMH_RequestHandler *rh, 71 struct MHD_Connection *connection, 72 struct TMH_HandlerContext *hc) 73 { 74 struct TMH_MerchantInstance *mi = hc->instance; 75 struct TALER_MERCHANTDB_WebhookDetails wb = { 0 }; 76 const char *webhook_id; 77 enum GNUNET_DB_QueryStatus qs; 78 struct GNUNET_JSON_Specification spec[] = { 79 GNUNET_JSON_spec_string ("webhook_id", 80 &webhook_id), 81 GNUNET_JSON_spec_string ("event_type", 82 (const char **) &wb.event_type), 83 TALER_JSON_spec_web_url ("url", 84 (const char **) &wb.url), 85 GNUNET_JSON_spec_string ("http_method", 86 (const char **) &wb.http_method), 87 GNUNET_JSON_spec_mark_optional ( 88 GNUNET_JSON_spec_string ("header_template", 89 (const char **) &wb.header_template), 90 NULL), 91 GNUNET_JSON_spec_mark_optional ( 92 GNUNET_JSON_spec_string ("body_template", 93 (const char **) &wb.body_template), 94 NULL), 95 GNUNET_JSON_spec_end () 96 }; 97 98 GNUNET_assert (NULL != mi); 99 { 100 enum GNUNET_GenericReturnValue res; 101 102 res = TALER_MHD_parse_json_data (connection, 103 hc->request_body, 104 spec); 105 if (GNUNET_OK != res) 106 { 107 GNUNET_break_op (0); 108 return (GNUNET_NO == res) 109 ? MHD_YES 110 : MHD_NO; 111 } 112 } 113 114 115 /* finally, interact with DB until no serialization error */ 116 for (unsigned int i = 0; i<MAX_RETRIES; i++) 117 { 118 /* Test if a webhook of this id is known */ 119 struct TALER_MERCHANTDB_WebhookDetails ewb; 120 121 if (GNUNET_OK != 122 TMH_db->start (TMH_db->cls, 123 "/post webhooks")) 124 { 125 GNUNET_break (0); 126 GNUNET_JSON_parse_free (spec); 127 return TALER_MHD_reply_with_error (connection, 128 MHD_HTTP_INTERNAL_SERVER_ERROR, 129 TALER_EC_GENERIC_DB_START_FAILED, 130 NULL); 131 } 132 qs = TMH_db->lookup_webhook (TMH_db->cls, 133 mi->settings.id, 134 webhook_id, 135 &ewb); 136 switch (qs) 137 { 138 case GNUNET_DB_STATUS_HARD_ERROR: 139 /* Clean up and fail hard */ 140 GNUNET_break (0); 141 TMH_db->rollback (TMH_db->cls); 142 GNUNET_JSON_parse_free (spec); 143 return TALER_MHD_reply_with_error (connection, 144 MHD_HTTP_INTERNAL_SERVER_ERROR, 145 TALER_EC_GENERIC_DB_FETCH_FAILED, 146 NULL); 147 case GNUNET_DB_STATUS_SOFT_ERROR: 148 /* restart transaction */ 149 goto retry; 150 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 151 /* Good, we can proceed! */ 152 break; 153 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 154 /* idempotency check: is ewb == wb? */ 155 { 156 bool eq; 157 158 eq = webhooks_equal (&wb, 159 &ewb); 160 TALER_MERCHANTDB_webhook_details_free (&ewb); 161 TMH_db->rollback (TMH_db->cls); 162 GNUNET_JSON_parse_free (spec); 163 return eq 164 ? TALER_MHD_reply_static (connection, 165 MHD_HTTP_NO_CONTENT, 166 NULL, 167 NULL, 168 0) 169 : TALER_MHD_reply_with_error (connection, 170 MHD_HTTP_CONFLICT, 171 TALER_EC_MERCHANT_PRIVATE_POST_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS, 172 webhook_id); 173 } 174 } /* end switch (qs) */ 175 176 qs = TMH_db->insert_webhook (TMH_db->cls, 177 mi->settings.id, 178 webhook_id, 179 &wb); 180 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 181 { 182 TMH_db->rollback (TMH_db->cls); 183 break; 184 } 185 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 186 { 187 qs = TMH_db->commit (TMH_db->cls); 188 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 189 break; 190 } 191 retry: 192 GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs); 193 TMH_db->rollback (TMH_db->cls); 194 } /* for RETRIES loop */ 195 GNUNET_JSON_parse_free (spec); 196 if (qs < 0) 197 { 198 GNUNET_break (0); 199 return TALER_MHD_reply_with_error ( 200 connection, 201 MHD_HTTP_INTERNAL_SERVER_ERROR, 202 (GNUNET_DB_STATUS_SOFT_ERROR == qs) 203 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 204 : TALER_EC_GENERIC_DB_COMMIT_FAILED, 205 NULL); 206 } 207 return TALER_MHD_reply_static (connection, 208 MHD_HTTP_NO_CONTENT, 209 NULL, 210 NULL, 211 0); 212 } 213 214 215 /* end of taler-merchant-httpd_private-post-webhooks.c */