Line data Source code
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 src/backend/taler-merchant-httpd_post-private-webhooks.c
22 : * @brief implementing POST /webhooks request handling
23 : * @author Priscilla HUANG
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_post-private-webhooks.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include <taler/taler_json_lib.h>
29 : #include "merchant-database/insert_webhook.h"
30 : #include "merchant-database/get_webhook.h"
31 : #include "merchant-database/start.h"
32 :
33 : /**
34 : * How often do we retry the simple INSERT database transaction?
35 : */
36 : #define MAX_RETRIES 3
37 :
38 :
39 : /**
40 : * Check if the two webhooks are identical.
41 : *
42 : * @param w1 webhook to compare
43 : * @param w2 other webhook to compare
44 : * @return true if they are 'equal', false if not or of payto_uris is not an array
45 : */
46 : static bool
47 4 : webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *w1,
48 : const struct TALER_MERCHANTDB_WebhookDetails *w2)
49 : {
50 4 : return ( (0 == strcmp (w1->event_type,
51 6 : w2->event_type)) &&
52 2 : (0 == strcmp (w1->url,
53 2 : w2->url)) &&
54 2 : (0 == strcmp (w1->http_method,
55 2 : w2->http_method)) &&
56 2 : ( ( (NULL == w1->header_template) &&
57 0 : (NULL == w2->header_template) ) ||
58 2 : ( (NULL != w1->header_template) &&
59 2 : (NULL != w2->header_template) &&
60 2 : (0 == strcmp (w1->header_template,
61 8 : w2->header_template)) ) ) &&
62 2 : ( ( (NULL == w1->body_template) &&
63 0 : (NULL == w2->body_template) ) ||
64 2 : ( (NULL != w1->body_template) &&
65 2 : (NULL != w2->body_template) &&
66 2 : (0 == strcmp (w1->body_template,
67 2 : w2->body_template)) ) ) );
68 : }
69 :
70 :
71 : enum MHD_Result
72 18 : TMH_private_post_webhooks (const struct TMH_RequestHandler *rh,
73 : struct MHD_Connection *connection,
74 : struct TMH_HandlerContext *hc)
75 : {
76 18 : struct TMH_MerchantInstance *mi = hc->instance;
77 18 : struct TALER_MERCHANTDB_WebhookDetails wb = { 0 };
78 : const char *webhook_id;
79 : enum GNUNET_DB_QueryStatus qs;
80 : struct GNUNET_JSON_Specification spec[] = {
81 18 : TALER_JSON_spec_slug ("webhook_id",
82 : &webhook_id),
83 18 : GNUNET_JSON_spec_string ("event_type",
84 : (const char **) &wb.event_type),
85 18 : TALER_JSON_spec_web_url ("url",
86 : (const char **) &wb.url),
87 18 : GNUNET_JSON_spec_string ("http_method",
88 : (const char **) &wb.http_method),
89 18 : GNUNET_JSON_spec_mark_optional (
90 : GNUNET_JSON_spec_string ("header_template",
91 : (const char **) &wb.header_template),
92 : NULL),
93 18 : GNUNET_JSON_spec_mark_optional (
94 : GNUNET_JSON_spec_string ("body_template",
95 : (const char **) &wb.body_template),
96 : NULL),
97 18 : GNUNET_JSON_spec_end ()
98 : };
99 :
100 18 : GNUNET_assert (NULL != mi);
101 : {
102 : enum GNUNET_GenericReturnValue res;
103 :
104 18 : res = TALER_MHD_parse_json_data (connection,
105 18 : hc->request_body,
106 : spec);
107 18 : if (GNUNET_OK != res)
108 : {
109 0 : GNUNET_break_op (0);
110 : return (GNUNET_NO == res)
111 : ? MHD_YES
112 0 : : MHD_NO;
113 : }
114 : }
115 :
116 :
117 : /* finally, interact with DB until no serialization error */
118 18 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
119 : {
120 : /* Test if a webhook of this id is known */
121 : struct TALER_MERCHANTDB_WebhookDetails ewb;
122 :
123 18 : if (GNUNET_OK !=
124 18 : TALER_MERCHANTDB_start (TMH_db,
125 : "/post webhooks"))
126 : {
127 0 : GNUNET_break (0);
128 0 : GNUNET_JSON_parse_free (spec);
129 4 : return TALER_MHD_reply_with_error (connection,
130 : MHD_HTTP_INTERNAL_SERVER_ERROR,
131 : TALER_EC_GENERIC_DB_START_FAILED,
132 : NULL);
133 : }
134 18 : qs = TALER_MERCHANTDB_get_webhook (TMH_db,
135 18 : mi->settings.id,
136 : webhook_id,
137 : &ewb);
138 18 : switch (qs)
139 : {
140 0 : case GNUNET_DB_STATUS_HARD_ERROR:
141 : /* Clean up and fail hard */
142 0 : GNUNET_break (0);
143 0 : TALER_MERCHANTDB_rollback (TMH_db);
144 0 : GNUNET_JSON_parse_free (spec);
145 0 : return TALER_MHD_reply_with_error (connection,
146 : MHD_HTTP_INTERNAL_SERVER_ERROR,
147 : TALER_EC_GENERIC_DB_FETCH_FAILED,
148 : NULL);
149 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
150 : /* restart transaction */
151 0 : goto retry;
152 14 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
153 : /* Good, we can proceed! */
154 14 : break;
155 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
156 : /* idempotency check: is ewb == wb? */
157 : {
158 : bool eq;
159 :
160 4 : eq = webhooks_equal (&wb,
161 : &ewb);
162 4 : TALER_MERCHANTDB_webhook_details_free (&ewb);
163 4 : TALER_MERCHANTDB_rollback (TMH_db);
164 4 : GNUNET_JSON_parse_free (spec);
165 : return eq
166 2 : ? TALER_MHD_reply_static (connection,
167 : MHD_HTTP_NO_CONTENT,
168 : NULL,
169 : NULL,
170 : 0)
171 6 : : TALER_MHD_reply_with_error (connection,
172 : MHD_HTTP_CONFLICT,
173 : TALER_EC_MERCHANT_PRIVATE_POST_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS,
174 : webhook_id);
175 : }
176 : } /* end switch (qs) */
177 :
178 14 : qs = TALER_MERCHANTDB_insert_webhook (TMH_db,
179 14 : mi->settings.id,
180 : webhook_id,
181 : &wb);
182 14 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
183 : {
184 0 : TALER_MERCHANTDB_rollback (TMH_db);
185 14 : break;
186 : }
187 14 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
188 : {
189 : /* A concurrent request created a webhook with the same ID
190 : between our lookup above and this INSERT. */
191 0 : TALER_MERCHANTDB_rollback (TMH_db);
192 0 : GNUNET_JSON_parse_free (spec);
193 0 : return TALER_MHD_reply_with_error (
194 : connection,
195 : MHD_HTTP_CONFLICT,
196 : TALER_EC_MERCHANT_PRIVATE_POST_WEBHOOKS_CONFLICT_WEBHOOK_EXISTS,
197 : webhook_id);
198 : }
199 14 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
200 : {
201 14 : qs = TALER_MERCHANTDB_commit (TMH_db);
202 14 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
203 14 : break;
204 : }
205 0 : retry:
206 0 : GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
207 0 : TALER_MERCHANTDB_rollback (TMH_db);
208 : } /* for RETRIES loop */
209 14 : GNUNET_JSON_parse_free (spec);
210 14 : if (qs < 0)
211 : {
212 0 : GNUNET_break (0);
213 0 : return TALER_MHD_reply_with_error (
214 : connection,
215 : MHD_HTTP_INTERNAL_SERVER_ERROR,
216 : (GNUNET_DB_STATUS_SOFT_ERROR == qs)
217 : ? TALER_EC_GENERIC_DB_SOFT_FAILURE
218 : : TALER_EC_GENERIC_DB_COMMIT_FAILED,
219 : NULL);
220 : }
221 14 : return TALER_MHD_reply_static (connection,
222 : MHD_HTTP_NO_CONTENT,
223 : NULL,
224 : NULL,
225 : 0);
226 : }
227 :
228 :
229 : /* end of taler-merchant-httpd_post-private-webhooks.c */
|