Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_kyc-webhook.c
18 : * @brief Handle notification of KYC completion via webhook.
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <gnunet/gnunet_json_lib.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h>
26 : #include <pthread.h>
27 : #include "taler_json_lib.h"
28 : #include "taler_mhd_lib.h"
29 : #include "taler_kyclogic_lib.h"
30 : #include "taler-exchange-httpd_kyc-webhook.h"
31 : #include "taler-exchange-httpd_responses.h"
32 :
33 :
34 : /**
35 : * Context for the webhook.
36 : */
37 : struct KycWebhookContext
38 : {
39 :
40 : /**
41 : * Kept in a DLL while suspended.
42 : */
43 : struct KycWebhookContext *next;
44 :
45 : /**
46 : * Kept in a DLL while suspended.
47 : */
48 : struct KycWebhookContext *prev;
49 :
50 : /**
51 : * Details about the connection we are processing.
52 : */
53 : struct TEH_RequestContext *rc;
54 :
55 : /**
56 : * Plugin responsible for the webhook.
57 : */
58 : struct TALER_KYCLOGIC_Plugin *plugin;
59 :
60 : /**
61 : * Section in the configuration of the configured
62 : * KYC provider.
63 : */
64 : const char *provider_section;
65 :
66 : /**
67 : * Configuration for the specific action.
68 : */
69 : struct TALER_KYCLOGIC_ProviderDetails *pd;
70 :
71 : /**
72 : * Webhook activity.
73 : */
74 : struct TALER_KYCLOGIC_WebhookHandle *wh;
75 :
76 : /**
77 : * HTTP response to return.
78 : */
79 : struct MHD_Response *response;
80 :
81 : /**
82 : * HTTP response code to return.
83 : */
84 : unsigned int response_code;
85 :
86 : /**
87 : * #GNUNET_YES if we are suspended,
88 : * #GNUNET_NO if not.
89 : * #GNUNET_SYSERR if we had some error.
90 : */
91 : enum GNUNET_GenericReturnValue suspended;
92 :
93 : };
94 :
95 :
96 : /**
97 : * Contexts are kept in a DLL while suspended.
98 : */
99 : static struct KycWebhookContext *kwh_head;
100 :
101 : /**
102 : * Contexts are kept in a DLL while suspended.
103 : */
104 : static struct KycWebhookContext *kwh_tail;
105 :
106 :
107 : /**
108 : * Resume processing the @a kwh request.
109 : *
110 : * @param kwh request to resume
111 : */
112 : static void
113 0 : kwh_resume (struct KycWebhookContext *kwh)
114 : {
115 0 : GNUNET_assert (GNUNET_YES == kwh->suspended);
116 0 : kwh->suspended = GNUNET_NO;
117 0 : GNUNET_CONTAINER_DLL_remove (kwh_head,
118 : kwh_tail,
119 : kwh);
120 0 : MHD_resume_connection (kwh->rc->connection);
121 0 : TALER_MHD_daemon_trigger ();
122 0 : }
123 :
124 :
125 : void
126 0 : TEH_kyc_webhook_cleanup (void)
127 : {
128 : struct KycWebhookContext *kwh;
129 :
130 0 : while (NULL != (kwh = kwh_head))
131 : {
132 0 : if (NULL != kwh->wh)
133 : {
134 0 : kwh->plugin->webhook_cancel (kwh->wh);
135 0 : kwh->wh = NULL;
136 : }
137 0 : kwh_resume (kwh);
138 : }
139 0 : }
140 :
141 :
142 : /**
143 : * Function called with the result of a webhook
144 : * operation.
145 : *
146 : * Note that the "decref" for the @a response
147 : * will be done by the plugin.
148 : *
149 : * @param cls closure
150 : * @param process_row legitimization process the webhook was about
151 : * @param account_id account the webhook was about
152 : * @param provider_section name of the configuration section of the logic that was run
153 : * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
154 : * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
155 : * @param status KYC status
156 : * @param expiration until when is the KYC check valid
157 : * @param http_status HTTP status code of @a response
158 : * @param[in] response to return to the HTTP client
159 : */
160 : static void
161 0 : webhook_finished_cb (
162 : void *cls,
163 : uint64_t process_row,
164 : const struct TALER_PaytoHashP *account_id,
165 : const char *provider_section,
166 : const char *provider_user_id,
167 : const char *provider_legitimization_id,
168 : enum TALER_KYCLOGIC_KycStatus status,
169 : struct GNUNET_TIME_Absolute expiration,
170 : unsigned int http_status,
171 : struct MHD_Response *response)
172 : {
173 0 : struct KycWebhookContext *kwh = cls;
174 :
175 0 : kwh->wh = NULL;
176 0 : switch (status)
177 : {
178 0 : case TALER_KYCLOGIC_STATUS_SUCCESS:
179 : /* _successfully_ resumed case */
180 : {
181 : enum GNUNET_DB_QueryStatus qs;
182 :
183 0 : qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
184 : process_row,
185 : provider_section,
186 : account_id,
187 : provider_user_id,
188 : provider_legitimization_id,
189 : expiration);
190 0 : if (qs < 0)
191 : {
192 0 : GNUNET_break (0);
193 0 : kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
194 : "set_kyc_ok");
195 0 : kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
196 0 : kwh_resume (kwh);
197 0 : return;
198 : }
199 : }
200 0 : break;
201 0 : default:
202 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
203 : "KYC status of %s/%s (Row #%llu) is %d\n",
204 : provider_user_id,
205 : provider_legitimization_id,
206 : (unsigned long long) process_row,
207 : status);
208 0 : break;
209 : }
210 0 : kwh->response = response;
211 0 : kwh->response_code = http_status;
212 0 : kwh_resume (kwh);
213 : }
214 :
215 :
216 : /**
217 : * Function called to clean up a context.
218 : *
219 : * @param rc request context
220 : */
221 : static void
222 0 : clean_kwh (struct TEH_RequestContext *rc)
223 : {
224 0 : struct KycWebhookContext *kwh = rc->rh_ctx;
225 :
226 0 : if (NULL != kwh->wh)
227 : {
228 0 : kwh->plugin->webhook_cancel (kwh->wh);
229 0 : kwh->wh = NULL;
230 : }
231 0 : if (NULL != kwh->response)
232 : {
233 0 : MHD_destroy_response (kwh->response);
234 0 : kwh->response = NULL;
235 : }
236 0 : GNUNET_free (kwh);
237 0 : }
238 :
239 :
240 : /**
241 : * Handle a (GET or POST) "/kyc-webhook" request.
242 : *
243 : * @param rc request to handle
244 : * @param method HTTP request method used by the client
245 : * @param root uploaded JSON body (can be NULL)
246 : * @param args one argument with the legitimization_uuid
247 : * @return MHD result code
248 : */
249 : static MHD_RESULT
250 0 : handler_kyc_webhook_generic (
251 : struct TEH_RequestContext *rc,
252 : const char *method,
253 : const json_t *root,
254 : const char *const args[])
255 : {
256 0 : struct KycWebhookContext *kwh = rc->rh_ctx;
257 :
258 0 : if (NULL == kwh)
259 : { /* first time */
260 0 : kwh = GNUNET_new (struct KycWebhookContext);
261 0 : kwh->rc = rc;
262 0 : rc->rh_ctx = kwh;
263 0 : rc->rh_cleaner = &clean_kwh;
264 :
265 0 : if (GNUNET_OK !=
266 0 : TALER_KYCLOGIC_lookup_logic (args[0],
267 : &kwh->plugin,
268 : &kwh->pd,
269 : &kwh->provider_section))
270 : {
271 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
272 : "KYC logic `%s' unknown (check KYC provider configuration)\n",
273 : args[0]);
274 0 : return TALER_MHD_reply_with_error (rc->connection,
275 : MHD_HTTP_NOT_FOUND,
276 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
277 : "$NAME");
278 : }
279 0 : kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
280 0 : kwh->pd,
281 0 : TEH_plugin->kyc_provider_account_lookup,
282 0 : TEH_plugin->cls,
283 : method,
284 : &args[1],
285 : rc->connection,
286 : root,
287 : &webhook_finished_cb,
288 : kwh);
289 0 : if (NULL == kwh->wh)
290 : {
291 0 : GNUNET_break_op (0);
292 0 : return TALER_MHD_reply_with_error (rc->connection,
293 : MHD_HTTP_INTERNAL_SERVER_ERROR,
294 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
295 : "failed to run webhook logic");
296 : }
297 0 : kwh->suspended = GNUNET_YES;
298 0 : GNUNET_CONTAINER_DLL_insert (kwh_head,
299 : kwh_tail,
300 : kwh);
301 0 : MHD_suspend_connection (rc->connection);
302 0 : return MHD_YES;
303 : }
304 :
305 0 : if (NULL != kwh->response)
306 : {
307 : /* handle _failed_ resumed cases */
308 0 : return MHD_queue_response (rc->connection,
309 : kwh->response_code,
310 : kwh->response);
311 : }
312 :
313 : /* We resumed, but got no response? This should
314 : not happen. */
315 0 : GNUNET_break (0);
316 0 : return TALER_MHD_reply_with_error (rc->connection,
317 : MHD_HTTP_INTERNAL_SERVER_ERROR,
318 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
319 : "resumed without response");
320 : }
321 :
322 :
323 : MHD_RESULT
324 0 : TEH_handler_kyc_webhook_get (
325 : struct TEH_RequestContext *rc,
326 : const char *const args[])
327 : {
328 0 : return handler_kyc_webhook_generic (rc,
329 : MHD_HTTP_METHOD_GET,
330 : NULL,
331 : args);
332 : }
333 :
334 :
335 : MHD_RESULT
336 0 : TEH_handler_kyc_webhook_post (
337 : struct TEH_RequestContext *rc,
338 : const json_t *root,
339 : const char *const args[])
340 : {
341 0 : return handler_kyc_webhook_generic (rc,
342 : MHD_HTTP_METHOD_POST,
343 : root,
344 : args);
345 : }
346 :
347 :
348 : /* end of taler-exchange-httpd_kyc-webhook.c */
|