Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2024 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 "taler/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/taler_attributes.h"
28 : #include "taler/taler_json_lib.h"
29 : #include "taler/taler_mhd_lib.h"
30 : #include "taler/taler_kyclogic_lib.h"
31 : #include "taler-exchange-httpd_common_kyc.h"
32 : #include "taler-exchange-httpd_kyc-webhook.h"
33 : #include "taler-exchange-httpd_responses.h"
34 :
35 :
36 : /**
37 : * Context for the webhook.
38 : */
39 : struct KycWebhookContext
40 : {
41 :
42 : /**
43 : * Kept in a DLL while suspended.
44 : */
45 : struct KycWebhookContext *next;
46 :
47 : /**
48 : * Kept in a DLL while suspended.
49 : */
50 : struct KycWebhookContext *prev;
51 :
52 : /**
53 : * Details about the connection we are processing.
54 : */
55 : struct TEH_RequestContext *rc;
56 :
57 : /**
58 : * Handle for the KYC-AML trigger interaction.
59 : */
60 : struct TEH_KycMeasureRunContext *kat;
61 :
62 : /**
63 : * Plugin responsible for the webhook.
64 : */
65 : struct TALER_KYCLOGIC_Plugin *plugin;
66 :
67 : /**
68 : * Name of the KYC provider (suffix of the
69 : * section name in the configuration).
70 : */
71 : const char *provider_name;
72 :
73 : /**
74 : * Configuration for the specific action.
75 : */
76 : struct TALER_KYCLOGIC_ProviderDetails *pd;
77 :
78 : /**
79 : * Webhook activity.
80 : */
81 : struct TALER_KYCLOGIC_WebhookHandle *wh;
82 :
83 : /**
84 : * Final HTTP response to return.
85 : */
86 : struct MHD_Response *response;
87 :
88 : /**
89 : * Final HTTP response code to return.
90 : */
91 : unsigned int response_code;
92 :
93 : /**
94 : * Response from the webhook plugin.
95 : *
96 : * Will become the final response on successfully
97 : * running the measure with the new attributes.
98 : */
99 : struct MHD_Response *webhook_response;
100 :
101 : /**
102 : * Response code to return for the webhook plugin
103 : * response.
104 : */
105 : unsigned int webhook_response_code;
106 :
107 : /**
108 : * #GNUNET_YES if we are suspended,
109 : * #GNUNET_NO if not.
110 : * #GNUNET_SYSERR if we had some error.
111 : */
112 : enum GNUNET_GenericReturnValue suspended;
113 :
114 : };
115 :
116 :
117 : /**
118 : * Contexts are kept in a DLL while suspended.
119 : */
120 : static struct KycWebhookContext *kwh_head;
121 :
122 : /**
123 : * Contexts are kept in a DLL while suspended.
124 : */
125 : static struct KycWebhookContext *kwh_tail;
126 :
127 :
128 : /**
129 : * Resume processing the @a kwh request.
130 : *
131 : * @param kwh request to resume
132 : */
133 : static void
134 0 : kwh_resume (struct KycWebhookContext *kwh)
135 : {
136 0 : GNUNET_assert (GNUNET_YES == kwh->suspended);
137 0 : kwh->suspended = GNUNET_NO;
138 0 : GNUNET_CONTAINER_DLL_remove (kwh_head,
139 : kwh_tail,
140 : kwh);
141 0 : MHD_resume_connection (kwh->rc->connection);
142 0 : TALER_MHD_daemon_trigger ();
143 0 : }
144 :
145 :
146 : void
147 0 : TEH_kyc_webhook_cleanup (void)
148 : {
149 : struct KycWebhookContext *kwh;
150 :
151 0 : while (NULL != (kwh = kwh_head))
152 : {
153 0 : if (NULL != kwh->wh)
154 : {
155 0 : kwh->plugin->webhook_cancel (kwh->wh);
156 0 : kwh->wh = NULL;
157 : }
158 0 : kwh_resume (kwh);
159 : }
160 0 : }
161 :
162 :
163 : /**
164 : * Function called after the KYC-AML trigger is done.
165 : *
166 : * @param cls closure with a `struct KycWebhookContext *`
167 : * @param ec error code or 0 on success
168 : * @param detail error message or NULL on success / no info
169 : */
170 : static void
171 0 : kyc_aml_webhook_finished (
172 : void *cls,
173 : enum TALER_ErrorCode ec,
174 : const char *detail)
175 : {
176 0 : struct KycWebhookContext *kwh = cls;
177 :
178 0 : kwh->kat = NULL;
179 0 : GNUNET_assert (NULL == kwh->response);
180 0 : if (TALER_EC_NONE != ec)
181 : {
182 0 : kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
183 0 : kwh->response = TALER_MHD_make_error (
184 : ec,
185 : detail
186 : );
187 : }
188 : else
189 : {
190 0 : GNUNET_assert (NULL != kwh->webhook_response);
191 0 : kwh->response_code = kwh->webhook_response_code;
192 0 : kwh->response = kwh->webhook_response;
193 0 : kwh->webhook_response = NULL;
194 0 : kwh->webhook_response_code = 0;
195 : }
196 0 : kwh_resume (kwh);
197 0 : }
198 :
199 :
200 : /**
201 : * Function called with the result of a KYC webhook operation.
202 : *
203 : * Note that the "decref" for the @a response
204 : * will be done by the plugin.
205 : *
206 : * @param cls closure
207 : * @param process_row legitimization process the webhook was about
208 : * @param account_id account the webhook was about
209 : * @param is_wallet true if @a account_id is for a wallet
210 : * @param provider_name name of the KYC provider that was run
211 : * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
212 : * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
213 : * @param status KYC status
214 : * @param expiration until when is the KYC check valid
215 : * @param attributes user attributes returned by the provider
216 : * @param http_status HTTP status code of @a response
217 : * @param[in] response to return to the HTTP client
218 : */
219 : static void
220 0 : webhook_finished_cb (
221 : void *cls,
222 : uint64_t process_row,
223 : const struct TALER_NormalizedPaytoHashP *account_id,
224 : bool is_wallet,
225 : const char *provider_name,
226 : const char *provider_user_id,
227 : const char *provider_legitimization_id,
228 : enum TALER_KYCLOGIC_KycStatus status,
229 : struct GNUNET_TIME_Absolute expiration,
230 : const json_t *attributes,
231 : unsigned int http_status,
232 : struct MHD_Response *response)
233 : {
234 0 : struct KycWebhookContext *kwh = cls;
235 : enum GNUNET_DB_QueryStatus qs;
236 :
237 0 : kwh->wh = NULL;
238 0 : kwh->webhook_response = response;
239 0 : kwh->webhook_response_code = http_status;
240 0 : switch (status)
241 : {
242 0 : case TALER_KYCLOGIC_STATUS_SUCCESS:
243 0 : qs = TEH_kyc_store_attributes (
244 : process_row,
245 : account_id,
246 : provider_name,
247 : provider_user_id,
248 : provider_legitimization_id,
249 : expiration,
250 : attributes);
251 0 : if (0 >= qs)
252 : {
253 0 : GNUNET_break (0);
254 0 : kyc_aml_webhook_finished (kwh,
255 : TALER_EC_GENERIC_DB_STORE_FAILED,
256 : "kyc_store_attributes");
257 0 : return;
258 : }
259 0 : kwh->kat = TEH_kyc_run_measure_for_attributes (
260 0 : &kwh->rc->async_scope_id,
261 : process_row,
262 : account_id,
263 : is_wallet,
264 : &kyc_aml_webhook_finished,
265 : kwh);
266 0 : if (NULL == kwh->kat)
267 : {
268 0 : kyc_aml_webhook_finished (kwh,
269 : TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
270 : "[exchange] AML_KYC_TRIGGER");
271 : }
272 0 : break;
273 0 : case TALER_KYCLOGIC_STATUS_FAILED:
274 : case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
275 : case TALER_KYCLOGIC_STATUS_USER_ABORTED:
276 : case TALER_KYCLOGIC_STATUS_ABORTED:
277 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
278 : "KYC process %s/%s (Row #%llu) failed: %d\n",
279 : provider_user_id,
280 : provider_legitimization_id,
281 : (unsigned long long) process_row,
282 : status);
283 0 : if (! TEH_kyc_failed (
284 : process_row,
285 : account_id,
286 : provider_name,
287 : provider_user_id,
288 : provider_legitimization_id,
289 : TALER_KYCLOGIC_status2s (status),
290 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
291 : {
292 0 : GNUNET_break (0);
293 0 : kyc_aml_webhook_finished (kwh,
294 : TALER_EC_GENERIC_DB_STORE_FAILED,
295 : "TEH_kyc_failed");
296 : }
297 0 : break;
298 0 : default:
299 0 : GNUNET_log (
300 : GNUNET_ERROR_TYPE_INFO,
301 : "KYC status of %s/%s (Row #%llu) is %d\n",
302 : provider_user_id,
303 : provider_legitimization_id,
304 : (unsigned long long) process_row,
305 : (int) status);
306 0 : kyc_aml_webhook_finished (kwh,
307 : TALER_EC_NONE,
308 : NULL);
309 0 : break;
310 : }
311 : }
312 :
313 :
314 : /**
315 : * Function called to clean up a context.
316 : *
317 : * @param rc request context
318 : */
319 : static void
320 0 : clean_kwh (struct TEH_RequestContext *rc)
321 : {
322 0 : struct KycWebhookContext *kwh = rc->rh_ctx;
323 :
324 0 : if (NULL != kwh->wh)
325 : {
326 0 : kwh->plugin->webhook_cancel (kwh->wh);
327 0 : kwh->wh = NULL;
328 : }
329 0 : if (NULL != kwh->kat)
330 : {
331 0 : TEH_kyc_run_measure_cancel (kwh->kat);
332 0 : kwh->kat = NULL;
333 : }
334 0 : if (NULL != kwh->response)
335 : {
336 0 : MHD_destroy_response (kwh->response);
337 0 : kwh->response = NULL;
338 : }
339 0 : if (NULL != kwh->webhook_response)
340 : {
341 0 : MHD_destroy_response (kwh->webhook_response);
342 0 : kwh->webhook_response = NULL;
343 : }
344 0 : GNUNET_free (kwh);
345 0 : }
346 :
347 :
348 : /**
349 : * Handle a (GET or POST) "/kyc-webhook" request.
350 : *
351 : * @param rc request to handle
352 : * @param method HTTP request method used by the client
353 : * @param root uploaded JSON body (can be NULL)
354 : * @param args one argument with the legitimization_uuid
355 : * @return MHD result code
356 : */
357 : static MHD_RESULT
358 0 : handler_kyc_webhook_generic (
359 : struct TEH_RequestContext *rc,
360 : const char *method,
361 : const json_t *root,
362 : const char *const args[])
363 : {
364 0 : struct KycWebhookContext *kwh = rc->rh_ctx;
365 :
366 0 : if (NULL == kwh)
367 : { /* first time */
368 0 : kwh = GNUNET_new (struct KycWebhookContext);
369 0 : kwh->rc = rc;
370 0 : rc->rh_ctx = kwh;
371 0 : rc->rh_cleaner = &clean_kwh;
372 :
373 0 : if ( (NULL == args[0]) ||
374 : (GNUNET_OK !=
375 0 : TALER_KYCLOGIC_lookup_logic (
376 : args[0],
377 : &kwh->plugin,
378 : &kwh->pd,
379 : &kwh->provider_name)) )
380 : {
381 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
382 : "KYC logic `%s' unknown (check KYC provider configuration)\n",
383 : args[0]);
384 0 : return TALER_MHD_reply_with_error (
385 : rc->connection,
386 : MHD_HTTP_NOT_FOUND,
387 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
388 : args[0]);
389 : }
390 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
391 : "KYC logic `%s' mapped to section %s\n",
392 : args[0],
393 : kwh->provider_name);
394 0 : kwh->wh = kwh->plugin->webhook (
395 0 : kwh->plugin->cls,
396 0 : kwh->pd,
397 0 : TEH_plugin->kyc_provider_account_lookup,
398 0 : TEH_plugin->cls,
399 : method,
400 : &args[1],
401 : rc->connection,
402 : root,
403 : &webhook_finished_cb,
404 : kwh);
405 0 : if (NULL == kwh->wh)
406 : {
407 0 : GNUNET_break_op (0);
408 0 : return TALER_MHD_reply_with_error (
409 : rc->connection,
410 : MHD_HTTP_INTERNAL_SERVER_ERROR,
411 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
412 : "failed to run webhook logic");
413 : }
414 0 : kwh->suspended = GNUNET_YES;
415 0 : GNUNET_CONTAINER_DLL_insert (kwh_head,
416 : kwh_tail,
417 : kwh);
418 0 : MHD_suspend_connection (rc->connection);
419 0 : return MHD_YES;
420 : }
421 0 : GNUNET_break (GNUNET_NO == kwh->suspended);
422 :
423 0 : if (NULL != kwh->response)
424 : {
425 : MHD_RESULT res;
426 :
427 0 : res = MHD_queue_response (rc->connection,
428 : kwh->response_code,
429 : kwh->response);
430 0 : GNUNET_break (MHD_YES == res);
431 0 : return res;
432 : }
433 :
434 : /* We resumed, but got no response? This should
435 : not happen. */
436 0 : GNUNET_break (0);
437 0 : return TALER_MHD_reply_with_error (
438 : rc->connection,
439 : MHD_HTTP_INTERNAL_SERVER_ERROR,
440 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
441 : "resumed without response");
442 : }
443 :
444 :
445 : MHD_RESULT
446 0 : TEH_handler_kyc_webhook_get (
447 : struct TEH_RequestContext *rc,
448 : const char *const args[])
449 : {
450 0 : return handler_kyc_webhook_generic (
451 : rc,
452 : MHD_HTTP_METHOD_GET,
453 : NULL,
454 : args);
455 : }
456 :
457 :
458 : MHD_RESULT
459 0 : TEH_handler_kyc_webhook_post (
460 : struct TEH_RequestContext *rc,
461 : const json_t *root,
462 : const char *const args[])
463 : {
464 0 : return handler_kyc_webhook_generic (
465 : rc,
466 : MHD_HTTP_METHOD_POST,
467 : root,
468 : args);
469 : }
470 :
471 :
472 : /* end of taler-exchange-httpd_kyc-webhook.c */
|