Line data Source code
1 : /*
2 : This file is part of GNU 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.GPL. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file plugin_kyclogic_kycaid.c
18 : * @brief kycaid for an authentication flow logic
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_kyclogic_lib.h"
22 : #include "taler/taler_kyclogic_plugin.h"
23 : #include "taler/taler_mhd_lib.h"
24 : #include "taler/taler_curl_lib.h"
25 : #include "taler/taler_json_lib.h"
26 : #include "taler/taler_templating_lib.h"
27 : #include <regex.h>
28 : #include "taler/taler_util.h"
29 :
30 : #define DEBUG 0
31 :
32 : /**
33 : * Saves the state of a plugin.
34 : */
35 : struct PluginState
36 : {
37 :
38 : /**
39 : * Our base URL.
40 : */
41 : char *exchange_base_url;
42 :
43 : /**
44 : * Our global configuration.
45 : */
46 : const struct GNUNET_CONFIGURATION_Handle *cfg;
47 :
48 : /**
49 : * Context for CURL operations (useful to the event loop)
50 : */
51 : struct GNUNET_CURL_Context *curl_ctx;
52 :
53 : /**
54 : * Context for integrating @e curl_ctx with the
55 : * GNUnet event loop.
56 : */
57 : struct GNUNET_CURL_RescheduleContext *curl_rc;
58 :
59 : };
60 :
61 :
62 : /**
63 : * Keeps the plugin-specific state for
64 : * a given configuration section.
65 : */
66 : struct TALER_KYCLOGIC_ProviderDetails
67 : {
68 :
69 : /**
70 : * Overall plugin state.
71 : */
72 : struct PluginState *ps;
73 :
74 : /**
75 : * Configuration section that configured us.
76 : */
77 : char *section;
78 :
79 : /**
80 : * Name of the provider, that is @e section without
81 : * the "kyc-provider-" prefix. This is the name used
82 : * in the database.
83 : */
84 : char *provider_name;
85 :
86 : /**
87 : * Authorization token to use when talking
88 : * to the service.
89 : */
90 : char *auth_token;
91 :
92 : /**
93 : * Form ID for the KYC check to perform.
94 : */
95 : char *form_id;
96 :
97 : /**
98 : * Helper binary to convert attributes returned by
99 : * KYCAID into our internal format.
100 : */
101 : char *conversion_helper;
102 :
103 : /**
104 : * Validity time for a successful KYC process.
105 : */
106 : struct GNUNET_TIME_Relative validity;
107 :
108 : /**
109 : * Curl-ready authentication header to use.
110 : */
111 : struct curl_slist *slist;
112 :
113 : };
114 :
115 :
116 : /**
117 : * Handle for an initiation operation.
118 : */
119 : struct TALER_KYCLOGIC_InitiateHandle
120 : {
121 :
122 : /**
123 : * Hash of the payto:// URI we are initiating
124 : * the KYC for.
125 : */
126 : struct TALER_NormalizedPaytoHashP h_payto;
127 :
128 : /**
129 : * UUID being checked.
130 : */
131 : uint64_t legitimization_uuid;
132 :
133 : /**
134 : * Our configuration details.
135 : */
136 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
137 :
138 : /**
139 : * Continuation to call.
140 : */
141 : TALER_KYCLOGIC_InitiateCallback cb;
142 :
143 : /**
144 : * Closure for @a cb.
145 : */
146 : void *cb_cls;
147 :
148 : /**
149 : * Context for #TEH_curl_easy_post(). Keeps the data that must
150 : * persist for Curl to make the upload.
151 : */
152 : struct TALER_CURL_PostContext ctx;
153 :
154 : /**
155 : * Handle for the request.
156 : */
157 : struct GNUNET_CURL_Job *job;
158 :
159 : /**
160 : * URL of the cURL request.
161 : */
162 : char *url;
163 :
164 : };
165 :
166 :
167 : /**
168 : * Handle for an KYC proof operation.
169 : */
170 : struct TALER_KYCLOGIC_ProofHandle
171 : {
172 :
173 : /**
174 : * Overall plugin state.
175 : */
176 : struct PluginState *ps;
177 :
178 : /**
179 : * Our configuration details.
180 : */
181 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
182 :
183 : /**
184 : * Continuation to call.
185 : */
186 : TALER_KYCLOGIC_ProofCallback cb;
187 :
188 : /**
189 : * Closure for @e cb.
190 : */
191 : void *cb_cls;
192 :
193 : /**
194 : * Connection we are handling.
195 : */
196 : struct MHD_Connection *connection;
197 :
198 : /**
199 : * Task for asynchronous execution.
200 : */
201 : struct GNUNET_SCHEDULER_Task *task;
202 : };
203 :
204 :
205 : /**
206 : * Handle for an KYC Web hook operation.
207 : */
208 : struct TALER_KYCLOGIC_WebhookHandle
209 : {
210 :
211 : /**
212 : * Continuation to call when done.
213 : */
214 : TALER_KYCLOGIC_WebhookCallback cb;
215 :
216 : /**
217 : * Closure for @a cb.
218 : */
219 : void *cb_cls;
220 :
221 : /**
222 : * Task for asynchronous execution.
223 : */
224 : struct GNUNET_SCHEDULER_Task *task;
225 :
226 : /**
227 : * Overall plugin state.
228 : */
229 : struct PluginState *ps;
230 :
231 : /**
232 : * Handle to helper process to extract attributes
233 : * we care about.
234 : */
235 : struct TALER_JSON_ExternalConversion *econ;
236 :
237 : /**
238 : * Our configuration details.
239 : */
240 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
241 :
242 : /**
243 : * Connection we are handling.
244 : */
245 : struct MHD_Connection *connection;
246 :
247 : /**
248 : * JSON response we got back, or NULL for none.
249 : */
250 : json_t *json_response;
251 :
252 : /**
253 : * Verification ID from the service.
254 : */
255 : char *verification_id;
256 :
257 : /**
258 : * Applicant ID from the service.
259 : */
260 : char *applicant_id;
261 :
262 : /**
263 : * URL of the cURL request.
264 : */
265 : char *url;
266 :
267 : /**
268 : * Handle for the request.
269 : */
270 : struct GNUNET_CURL_Job *job;
271 :
272 : /**
273 : * Response to return asynchronously.
274 : */
275 : struct MHD_Response *resp;
276 :
277 : /**
278 : * Our account ID.
279 : */
280 : struct TALER_NormalizedPaytoHashP h_payto;
281 :
282 : /**
283 : * Row in legitimizations for the given
284 : * @e verification_id.
285 : */
286 : uint64_t process_row;
287 :
288 : /**
289 : * HTTP response code we got from KYCAID.
290 : */
291 : unsigned int kycaid_response_code;
292 :
293 : /**
294 : * HTTP response code to return asynchronously.
295 : */
296 : unsigned int response_code;
297 :
298 : /**
299 : * True if @e h_payto is for a wallet.
300 : */
301 : bool is_wallet;
302 : };
303 :
304 :
305 : /**
306 : * Release configuration resources previously loaded
307 : *
308 : * @param[in] pd configuration to release
309 : */
310 : static void
311 61 : kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
312 : {
313 61 : curl_slist_free_all (pd->slist);
314 61 : GNUNET_free (pd->conversion_helper);
315 61 : GNUNET_free (pd->auth_token);
316 61 : GNUNET_free (pd->form_id);
317 61 : GNUNET_free (pd->section);
318 61 : GNUNET_free (pd->provider_name);
319 61 : GNUNET_free (pd);
320 61 : }
321 :
322 :
323 : /**
324 : * Load the configuration of the KYC provider.
325 : *
326 : * @param cls closure
327 : * @param provider_section_name configuration section to parse
328 : * @return NULL if configuration is invalid
329 : */
330 : static struct TALER_KYCLOGIC_ProviderDetails *
331 61 : kycaid_load_configuration (void *cls,
332 : const char *provider_section_name)
333 : {
334 61 : struct PluginState *ps = cls;
335 : struct TALER_KYCLOGIC_ProviderDetails *pd;
336 :
337 61 : pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
338 61 : pd->ps = ps;
339 61 : pd->section = GNUNET_strdup (provider_section_name);
340 : pd->provider_name
341 61 : = GNUNET_strdup (
342 : (0 == strncasecmp (provider_section_name,
343 : "kyc-provider-",
344 : strlen ("kyc-provider-")))
345 : ? &provider_section_name[strlen ("kyc-provider-")]
346 : : provider_section_name);
347 61 : if (GNUNET_OK !=
348 61 : GNUNET_CONFIGURATION_get_value_time (ps->cfg,
349 : provider_section_name,
350 : "KYC_KYCAID_VALIDITY",
351 : &pd->validity))
352 : {
353 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
354 : provider_section_name,
355 : "KYC_KYCAID_VALIDITY");
356 0 : kycaid_unload_configuration (pd);
357 0 : return NULL;
358 : }
359 61 : if (GNUNET_OK !=
360 61 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
361 : provider_section_name,
362 : "KYC_KYCAID_AUTH_TOKEN",
363 : &pd->auth_token))
364 : {
365 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
366 : provider_section_name,
367 : "KYC_KYCAID_AUTH_TOKEN");
368 0 : kycaid_unload_configuration (pd);
369 0 : return NULL;
370 : }
371 61 : if (GNUNET_OK !=
372 61 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
373 : provider_section_name,
374 : "KYC_KYCAID_FORM_ID",
375 : &pd->form_id))
376 : {
377 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
378 : provider_section_name,
379 : "KYC_KYCAID_FORM_ID");
380 0 : kycaid_unload_configuration (pd);
381 0 : return NULL;
382 : }
383 61 : if (GNUNET_OK !=
384 61 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
385 : provider_section_name,
386 : "KYC_KYCAID_CONVERTER_HELPER",
387 : &pd->conversion_helper))
388 : {
389 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
390 : provider_section_name,
391 : "KYC_KYCAID_CONVERTER_HELPER");
392 0 : kycaid_unload_configuration (pd);
393 0 : return NULL;
394 : }
395 : {
396 : char *auth;
397 :
398 61 : GNUNET_asprintf (&auth,
399 : "%s: Token %s",
400 : MHD_HTTP_HEADER_AUTHORIZATION,
401 : pd->auth_token);
402 61 : pd->slist = curl_slist_append (NULL,
403 : auth);
404 61 : GNUNET_free (auth);
405 : }
406 61 : return pd;
407 : }
408 :
409 :
410 : /**
411 : * Cancel KYC check initiation.
412 : *
413 : * @param[in] ih handle of operation to cancel
414 : */
415 : static void
416 0 : kycaid_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
417 : {
418 0 : if (NULL != ih->job)
419 : {
420 0 : GNUNET_CURL_job_cancel (ih->job);
421 0 : ih->job = NULL;
422 : }
423 0 : GNUNET_free (ih->url);
424 0 : TALER_curl_easy_post_finished (&ih->ctx);
425 0 : GNUNET_free (ih);
426 0 : }
427 :
428 :
429 : /**
430 : * Function called when we're done processing the
431 : * HTTP "/forms/{form_id}/urls" request.
432 : *
433 : * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
434 : * @param response_code HTTP response code, 0 on error
435 : * @param response parsed JSON result, NULL on error
436 : */
437 : static void
438 0 : handle_initiate_finished (void *cls,
439 : long response_code,
440 : const void *response)
441 : {
442 0 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
443 0 : const json_t *j = response;
444 :
445 0 : ih->job = NULL;
446 0 : switch (response_code)
447 : {
448 0 : case MHD_HTTP_OK:
449 : {
450 : const char *verification_id;
451 : const char *form_url;
452 : const char *form_id;
453 : struct GNUNET_JSON_Specification spec[] = {
454 0 : GNUNET_JSON_spec_string ("verification_id",
455 : &verification_id),
456 0 : GNUNET_JSON_spec_string ("form_url",
457 : &form_url),
458 0 : GNUNET_JSON_spec_string ("form_id",
459 : &form_id),
460 0 : GNUNET_JSON_spec_end ()
461 : };
462 :
463 0 : if (GNUNET_OK !=
464 0 : GNUNET_JSON_parse (j,
465 : spec,
466 : NULL, NULL))
467 : {
468 0 : GNUNET_break_op (0);
469 : #if DEBUG
470 : json_dumpf (j,
471 : stderr,
472 : JSON_INDENT (2));
473 : #endif
474 0 : ih->cb (ih->cb_cls,
475 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
476 : NULL,
477 : NULL,
478 : NULL,
479 0 : json_string_value (json_object_get (j,
480 : "type")));
481 0 : break;
482 : }
483 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
484 : "Started new verification `%s' using form %s\n",
485 : verification_id,
486 : form_id);
487 0 : ih->cb (ih->cb_cls,
488 : TALER_EC_NONE,
489 : form_url,
490 : NULL, /* no provider_user_id */
491 : verification_id,
492 : NULL /* no error */);
493 0 : GNUNET_JSON_parse_free (spec);
494 : }
495 0 : break;
496 0 : case MHD_HTTP_BAD_REQUEST:
497 : case MHD_HTTP_NOT_FOUND:
498 : case MHD_HTTP_CONFLICT:
499 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
500 : "KYCAID failed with response %u:\n",
501 : (unsigned int) response_code);
502 : #if DEBUG
503 : json_dumpf (j,
504 : stderr,
505 : JSON_INDENT (2));
506 : #endif
507 0 : ih->cb (ih->cb_cls,
508 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
509 : NULL,
510 : NULL,
511 : NULL,
512 0 : json_string_value (json_object_get (j,
513 : "type")));
514 0 : break;
515 0 : case MHD_HTTP_UNAUTHORIZED:
516 : case MHD_HTTP_PAYMENT_REQUIRED:
517 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
518 : "Refused access with HTTP status code %u\n",
519 : (unsigned int) response_code);
520 0 : ih->cb (ih->cb_cls,
521 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED,
522 : NULL,
523 : NULL,
524 : NULL,
525 0 : json_string_value (json_object_get (j,
526 : "type")));
527 0 : break;
528 0 : case MHD_HTTP_REQUEST_TIMEOUT:
529 0 : ih->cb (ih->cb_cls,
530 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT,
531 : NULL,
532 : NULL,
533 : NULL,
534 0 : json_string_value (json_object_get (j,
535 : "type")));
536 0 : break;
537 0 : case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
538 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
539 : "KYCAID failed with response %u:\n",
540 : (unsigned int) response_code);
541 : #if DEBUG
542 : json_dumpf (j,
543 : stderr,
544 : JSON_INDENT (2));
545 : #endif
546 0 : ih->cb (ih->cb_cls,
547 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
548 : NULL,
549 : NULL,
550 : NULL,
551 0 : json_string_value (json_object_get (j,
552 : "type")));
553 0 : break;
554 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
555 0 : ih->cb (ih->cb_cls,
556 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
557 : NULL,
558 : NULL,
559 : NULL,
560 0 : json_string_value (json_object_get (j,
561 : "type")));
562 0 : break;
563 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
564 0 : ih->cb (ih->cb_cls,
565 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
566 : NULL,
567 : NULL,
568 : NULL,
569 0 : json_string_value (json_object_get (j,
570 : "type")));
571 0 : break;
572 0 : default:
573 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
574 : "Unexpected KYCAID response %u:\n",
575 : (unsigned int) response_code);
576 : #if DEBUG
577 : json_dumpf (j,
578 : stderr,
579 : JSON_INDENT (2));
580 : #endif
581 0 : ih->cb (ih->cb_cls,
582 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
583 : NULL,
584 : NULL,
585 : NULL,
586 0 : json_string_value (json_object_get (j,
587 : "type")));
588 0 : break;
589 : }
590 0 : kycaid_initiate_cancel (ih);
591 0 : }
592 :
593 :
594 : /**
595 : * Initiate KYC check.
596 : *
597 : * @param cls the @e cls of this struct with the plugin-specific state
598 : * @param pd provider configuration details
599 : * @param account_id which account to trigger process for
600 : * @param legitimization_uuid unique ID for the legitimization process
601 : * @param context additional contextual information for the legi process
602 : * @param cb function to call with the result
603 : * @param cb_cls closure for @a cb
604 : * @return handle to cancel operation early
605 : */
606 : static struct TALER_KYCLOGIC_InitiateHandle *
607 0 : kycaid_initiate (void *cls,
608 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
609 : const struct TALER_NormalizedPaytoHashP *account_id,
610 : uint64_t legitimization_uuid,
611 : const json_t *context,
612 : TALER_KYCLOGIC_InitiateCallback cb,
613 : void *cb_cls)
614 : {
615 0 : struct PluginState *ps = cls;
616 : struct TALER_KYCLOGIC_InitiateHandle *ih;
617 : json_t *body;
618 : CURL *eh;
619 :
620 : (void) context;
621 0 : eh = curl_easy_init ();
622 0 : if (NULL == eh)
623 : {
624 0 : GNUNET_break (0);
625 0 : return NULL;
626 : }
627 0 : ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
628 0 : ih->legitimization_uuid = legitimization_uuid;
629 0 : ih->cb = cb;
630 0 : ih->cb_cls = cb_cls;
631 0 : ih->h_payto = *account_id;
632 0 : ih->pd = pd;
633 0 : GNUNET_asprintf (&ih->url,
634 : "https://api.kycaid.com/forms/%s/urls",
635 0 : pd->form_id);
636 0 : body = GNUNET_JSON_PACK (
637 : GNUNET_JSON_pack_data64_auto ("external_applicant_id",
638 : account_id)
639 : );
640 0 : GNUNET_break (CURLE_OK ==
641 : curl_easy_setopt (eh,
642 : CURLOPT_VERBOSE,
643 : 0));
644 0 : GNUNET_assert (CURLE_OK ==
645 : curl_easy_setopt (eh,
646 : CURLOPT_MAXREDIRS,
647 : 1L));
648 0 : GNUNET_break (CURLE_OK ==
649 : curl_easy_setopt (eh,
650 : CURLOPT_URL,
651 : ih->url));
652 0 : if (GNUNET_OK !=
653 0 : TALER_curl_easy_post (&ih->ctx,
654 : eh,
655 : body))
656 : {
657 0 : GNUNET_break (0);
658 0 : GNUNET_free (ih->url);
659 0 : GNUNET_free (ih);
660 0 : curl_easy_cleanup (eh);
661 0 : json_decref (body);
662 0 : return NULL;
663 : }
664 0 : json_decref (body);
665 0 : ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
666 : eh,
667 0 : ih->ctx.headers,
668 : &handle_initiate_finished,
669 : ih);
670 0 : GNUNET_CURL_extend_headers (ih->job,
671 0 : pd->slist);
672 0 : return ih;
673 : }
674 :
675 :
676 : /**
677 : * Cancel KYC proof.
678 : *
679 : * @param[in] ph handle of operation to cancel
680 : */
681 : static void
682 0 : kycaid_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
683 : {
684 0 : if (NULL != ph->task)
685 : {
686 0 : GNUNET_SCHEDULER_cancel (ph->task);
687 0 : ph->task = NULL;
688 : }
689 0 : GNUNET_free (ph);
690 0 : }
691 :
692 :
693 : /**
694 : * Call @a ph callback with HTTP error response.
695 : *
696 : * @param cls proof handle to generate reply for
697 : */
698 : static void
699 0 : proof_reply (void *cls)
700 : {
701 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
702 : struct MHD_Response *resp;
703 : enum GNUNET_GenericReturnValue ret;
704 : json_t *body;
705 : unsigned int http_status;
706 :
707 0 : http_status = MHD_HTTP_BAD_REQUEST;
708 0 : body = GNUNET_JSON_PACK (
709 : TALER_JSON_pack_ec (TALER_EC_GENERIC_ENDPOINT_UNKNOWN));
710 0 : GNUNET_assert (NULL != body);
711 0 : ret = TALER_TEMPLATING_build (ph->connection,
712 : &http_status,
713 : "kycaid-invalid-request",
714 : NULL,
715 : NULL,
716 : body,
717 : &resp);
718 0 : json_decref (body);
719 0 : if (GNUNET_SYSERR == ret)
720 : {
721 0 : resp = NULL;
722 0 : GNUNET_break (0);
723 : }
724 : else
725 : {
726 0 : GNUNET_break (MHD_NO !=
727 : MHD_add_response_header (resp,
728 : MHD_HTTP_HEADER_CONTENT_TYPE,
729 : "text/html"));
730 : }
731 0 : ph->cb (ph->cb_cls,
732 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
733 0 : ph->pd->provider_name,
734 : NULL, /* user id */
735 : NULL, /* provider legi ID */
736 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
737 : NULL, /* attributes */
738 : http_status,
739 : resp);
740 0 : }
741 :
742 :
743 : /**
744 : * Check KYC status and return status to human. Not
745 : * used by KYC AID!
746 : *
747 : * @param cls the @e cls of this struct with the plugin-specific state
748 : * @param pd provider configuration details
749 : * @param connection MHD connection object (for HTTP headers)
750 : * @param account_id which account to trigger process for
751 : * @param process_row row in the legitimization processes table the legitimization is for
752 : * @param provider_user_id user ID (or NULL) the proof is for
753 : * @param provider_legitimization_id legitimization ID the proof is for
754 : * @param cb function to call with the result
755 : * @param cb_cls closure for @a cb
756 : * @return handle to cancel operation early
757 : */
758 : static struct TALER_KYCLOGIC_ProofHandle *
759 0 : kycaid_proof (void *cls,
760 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
761 : struct MHD_Connection *connection,
762 : const struct TALER_NormalizedPaytoHashP *account_id,
763 : uint64_t process_row,
764 : const char *provider_user_id,
765 : const char *provider_legitimization_id,
766 : TALER_KYCLOGIC_ProofCallback cb,
767 : void *cb_cls)
768 : {
769 0 : struct PluginState *ps = cls;
770 : struct TALER_KYCLOGIC_ProofHandle *ph;
771 :
772 0 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
773 0 : ph->ps = ps;
774 0 : ph->pd = pd;
775 0 : ph->cb = cb;
776 0 : ph->cb_cls = cb_cls;
777 0 : ph->connection = connection;
778 0 : ph->task = GNUNET_SCHEDULER_add_now (&proof_reply,
779 : ph);
780 0 : return ph;
781 : }
782 :
783 :
784 : /**
785 : * Cancel KYC webhook execution.
786 : *
787 : * @param[in] wh handle of operation to cancel
788 : */
789 : static void
790 0 : kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
791 : {
792 0 : if (NULL != wh->task)
793 : {
794 0 : GNUNET_SCHEDULER_cancel (wh->task);
795 0 : wh->task = NULL;
796 : }
797 0 : if (NULL != wh->econ)
798 : {
799 0 : TALER_JSON_external_conversion_stop (wh->econ);
800 0 : wh->econ = NULL;
801 : }
802 0 : if (NULL != wh->job)
803 : {
804 0 : GNUNET_CURL_job_cancel (wh->job);
805 0 : wh->job = NULL;
806 : }
807 0 : if (NULL != wh->json_response)
808 : {
809 0 : json_decref (wh->json_response);
810 0 : wh->json_response = NULL;
811 : }
812 0 : GNUNET_free (wh->verification_id);
813 0 : GNUNET_free (wh->applicant_id);
814 0 : GNUNET_free (wh->url);
815 0 : GNUNET_free (wh);
816 0 : }
817 :
818 :
819 : /**
820 : * Extract KYC failure reasons and log those
821 : *
822 : * @param verifications JSON object with failure details
823 : */
824 : static void
825 0 : log_failure (const json_t *verifications)
826 : {
827 : const json_t *member;
828 : const char *name;
829 :
830 0 : json_object_foreach ((json_t *) verifications, name, member)
831 : {
832 : bool iverified;
833 : const char *comment;
834 : struct GNUNET_JSON_Specification spec[] = {
835 0 : GNUNET_JSON_spec_bool ("verified",
836 : &iverified),
837 0 : GNUNET_JSON_spec_string ("comment",
838 : &comment),
839 0 : GNUNET_JSON_spec_end ()
840 : };
841 :
842 0 : if (GNUNET_OK !=
843 0 : GNUNET_JSON_parse (member,
844 : spec,
845 : NULL, NULL))
846 : {
847 0 : GNUNET_break_op (0);
848 : #if DEBUG
849 : json_dumpf (member,
850 : stderr,
851 : JSON_INDENT (2));
852 : #endif
853 0 : continue;
854 : }
855 0 : if (iverified)
856 0 : continue;
857 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
858 : "KYC verification of attribute `%s' failed: %s\n",
859 : name,
860 : comment);
861 : }
862 0 : }
863 :
864 :
865 : /**
866 : * Type of a callback that receives a JSON @a result.
867 : *
868 : * @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
869 : * @param status_type how did the process die
870 : * @param code termination status code from the process
871 : * @param result converted attribute data, NULL on failure
872 : */
873 : static void
874 0 : webhook_conversion_cb (void *cls,
875 : enum GNUNET_OS_ProcessStatusType status_type,
876 : unsigned long code,
877 : const json_t *result)
878 : {
879 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
880 : struct GNUNET_TIME_Absolute expiration;
881 : struct MHD_Response *resp;
882 :
883 0 : wh->econ = NULL;
884 0 : if ( (GNUNET_OS_PROCESS_EXITED == status_type) &&
885 0 : (0 == code) &&
886 : (NULL == result) )
887 : {
888 : /* No result, but *our helper* was OK => bad input */
889 0 : GNUNET_break_op (0);
890 : #if DEBUG
891 : json_dumpf (wh->json_response,
892 : stderr,
893 : JSON_INDENT (2));
894 : #endif
895 0 : resp = TALER_MHD_MAKE_JSON_PACK (
896 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
897 : wh->kycaid_response_code),
898 : GNUNET_JSON_pack_object_incref ("kycaid_body",
899 : (json_t *) wh->json_response));
900 0 : wh->cb (wh->cb_cls,
901 : wh->process_row,
902 0 : &wh->h_payto,
903 0 : wh->is_wallet,
904 0 : wh->pd->provider_name,
905 0 : wh->applicant_id,
906 0 : wh->verification_id,
907 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
908 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
909 : NULL,
910 : MHD_HTTP_BAD_GATEWAY,
911 : resp);
912 0 : kycaid_webhook_cancel (wh);
913 0 : return;
914 : }
915 0 : if ( (NULL == result) ||
916 0 : (GNUNET_OS_PROCESS_EXITED != status_type) ||
917 : (0 != code) )
918 : {
919 : /* Failure in our helper */
920 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
921 : "Helper died with status %d/%d\n",
922 : (int) status_type,
923 : (int) code);
924 : #if DEBUG
925 : json_dumpf (wh->json_response,
926 : stderr,
927 : JSON_INDENT (2));
928 : #endif
929 0 : resp = TALER_MHD_MAKE_JSON_PACK (
930 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
931 : wh->kycaid_response_code),
932 : GNUNET_JSON_pack_object_incref ("kycaid_body",
933 : (json_t *) wh->json_response));
934 0 : wh->cb (wh->cb_cls,
935 : wh->process_row,
936 0 : &wh->h_payto,
937 0 : wh->is_wallet,
938 0 : wh->pd->provider_name,
939 0 : wh->applicant_id,
940 0 : wh->verification_id,
941 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
942 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
943 : NULL,
944 : MHD_HTTP_BAD_GATEWAY,
945 : resp);
946 0 : kycaid_webhook_cancel (wh);
947 0 : return;
948 : }
949 0 : if (! json_is_string (json_object_get (result,
950 : "FORM_ID")))
951 : {
952 : /* Failure in our helper */
953 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
954 : "Mandatory FORM_ID not set in result\n");
955 : #if DEBUG
956 : json_dumpf (result,
957 : stderr,
958 : JSON_INDENT (2));
959 : #endif
960 0 : resp = TALER_MHD_MAKE_JSON_PACK (
961 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
962 : wh->kycaid_response_code),
963 : GNUNET_JSON_pack_object_incref ("kycaid_body",
964 : (json_t *) wh->json_response));
965 0 : wh->cb (wh->cb_cls,
966 : wh->process_row,
967 0 : &wh->h_payto,
968 0 : wh->is_wallet,
969 0 : wh->pd->provider_name,
970 0 : wh->applicant_id,
971 0 : wh->verification_id,
972 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
973 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
974 : NULL,
975 : MHD_HTTP_BAD_GATEWAY,
976 : resp);
977 0 : kycaid_webhook_cancel (wh);
978 0 : return;
979 : }
980 :
981 0 : expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
982 0 : resp = MHD_create_response_from_buffer_static (0,
983 : "");
984 0 : wh->cb (wh->cb_cls,
985 : wh->process_row,
986 0 : &wh->h_payto,
987 0 : wh->is_wallet,
988 0 : wh->pd->provider_name,
989 0 : wh->applicant_id,
990 0 : wh->verification_id,
991 : TALER_KYCLOGIC_STATUS_SUCCESS,
992 : expiration,
993 : result,
994 : MHD_HTTP_NO_CONTENT,
995 : resp);
996 0 : kycaid_webhook_cancel (wh);
997 : }
998 :
999 :
1000 : /**
1001 : * Function called when we're done processing the
1002 : * HTTP "/applicants/{verification_id}" request.
1003 : *
1004 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
1005 : * @param response_code HTTP response code, 0 on error
1006 : * @param response parsed JSON result, NULL on error
1007 : */
1008 : static void
1009 0 : handle_webhook_finished (void *cls,
1010 : long response_code,
1011 : const void *response)
1012 : {
1013 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1014 0 : const json_t *j = response;
1015 : struct MHD_Response *resp;
1016 :
1017 0 : wh->job = NULL;
1018 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1019 : "Webhook returned with HTTP status %u\n",
1020 : (unsigned int) response_code);
1021 0 : wh->kycaid_response_code = response_code;
1022 0 : wh->json_response = json_incref ((json_t *) j);
1023 0 : switch (response_code)
1024 : {
1025 0 : case MHD_HTTP_OK:
1026 : {
1027 : const char *profile_status;
1028 :
1029 0 : profile_status = json_string_value (
1030 0 : json_object_get (
1031 : j,
1032 : "profile_status"));
1033 0 : if (NULL == profile_status)
1034 : {
1035 0 : GNUNET_break_op (0);
1036 0 : profile_status = "<invalid>";
1037 : }
1038 0 : if (0 != strcasecmp ("valid",
1039 : profile_status))
1040 : {
1041 : enum TALER_KYCLOGIC_KycStatus ks;
1042 :
1043 0 : ks = (0 == strcasecmp ("pending",
1044 : profile_status))
1045 : ? TALER_KYCLOGIC_STATUS_PENDING
1046 0 : : TALER_KYCLOGIC_STATUS_USER_ABORTED;
1047 0 : resp = MHD_create_response_from_buffer_static (0,
1048 : "");
1049 0 : wh->cb (wh->cb_cls,
1050 : wh->process_row,
1051 0 : &wh->h_payto,
1052 0 : wh->is_wallet,
1053 0 : wh->pd->provider_name,
1054 0 : wh->applicant_id,
1055 0 : wh->verification_id,
1056 : ks,
1057 0 : GNUNET_TIME_UNIT_ZERO_ABS,
1058 : NULL,
1059 : MHD_HTTP_NO_CONTENT,
1060 : resp);
1061 0 : break;
1062 : }
1063 : {
1064 0 : const char *argv[] = {
1065 0 : wh->pd->conversion_helper,
1066 : "-a",
1067 0 : wh->pd->auth_token,
1068 : NULL,
1069 : };
1070 :
1071 : wh->econ
1072 0 : = TALER_JSON_external_conversion_start (
1073 : j,
1074 : &webhook_conversion_cb,
1075 : wh,
1076 0 : wh->pd->conversion_helper,
1077 : argv);
1078 : }
1079 0 : if (NULL == wh->econ)
1080 : {
1081 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1082 : "Failed to start KYCAID conversion helper `%s'\n",
1083 : wh->pd->conversion_helper);
1084 0 : resp = TALER_MHD_make_error (
1085 : TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED,
1086 : NULL);
1087 0 : wh->cb (wh->cb_cls,
1088 : wh->process_row,
1089 0 : &wh->h_payto,
1090 0 : wh->is_wallet,
1091 0 : wh->pd->provider_name,
1092 0 : wh->applicant_id,
1093 0 : wh->verification_id,
1094 : TALER_KYCLOGIC_STATUS_INTERNAL_ERROR,
1095 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1096 : NULL,
1097 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1098 : resp);
1099 0 : break;
1100 : }
1101 0 : return;
1102 : }
1103 : break;
1104 0 : case MHD_HTTP_BAD_REQUEST:
1105 : case MHD_HTTP_NOT_FOUND:
1106 : case MHD_HTTP_CONFLICT:
1107 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1108 : "KYCAID failed with response %u:\n",
1109 : (unsigned int) response_code);
1110 : #if DEBUG
1111 : json_dumpf (j,
1112 : stderr,
1113 : JSON_INDENT (2));
1114 : #endif
1115 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1116 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1117 : response_code));
1118 0 : wh->cb (wh->cb_cls,
1119 : wh->process_row,
1120 0 : &wh->h_payto,
1121 0 : wh->is_wallet,
1122 0 : wh->pd->provider_name,
1123 0 : wh->applicant_id,
1124 0 : wh->verification_id,
1125 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1126 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1127 : NULL,
1128 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1129 : resp);
1130 0 : break;
1131 0 : case MHD_HTTP_UNAUTHORIZED:
1132 : case MHD_HTTP_PAYMENT_REQUIRED:
1133 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1134 : "Refused access with HTTP status code %u\n",
1135 : (unsigned int) response_code);
1136 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1137 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1138 : response_code),
1139 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1140 : (json_t *) j));
1141 0 : wh->cb (wh->cb_cls,
1142 : wh->process_row,
1143 0 : &wh->h_payto,
1144 0 : wh->is_wallet,
1145 0 : wh->pd->provider_name,
1146 0 : wh->applicant_id,
1147 0 : wh->verification_id,
1148 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1149 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1150 : NULL,
1151 : MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
1152 : resp);
1153 0 : break;
1154 0 : case MHD_HTTP_REQUEST_TIMEOUT:
1155 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1156 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1157 : response_code),
1158 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1159 : (json_t *) j));
1160 0 : wh->cb (wh->cb_cls,
1161 : wh->process_row,
1162 0 : &wh->h_payto,
1163 0 : wh->is_wallet,
1164 0 : wh->pd->provider_name,
1165 0 : wh->applicant_id,
1166 0 : wh->verification_id,
1167 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1168 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1169 : NULL,
1170 : MHD_HTTP_GATEWAY_TIMEOUT,
1171 : resp);
1172 0 : break;
1173 0 : case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
1174 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1175 : "KYCAID failed with response %u:\n",
1176 : (unsigned int) response_code);
1177 : #if DEBUG
1178 : json_dumpf (j,
1179 : stderr,
1180 : JSON_INDENT (2));
1181 : #endif
1182 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1183 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1184 : response_code),
1185 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1186 : (json_t *) j));
1187 0 : wh->cb (wh->cb_cls,
1188 : wh->process_row,
1189 0 : &wh->h_payto,
1190 0 : wh->is_wallet,
1191 0 : wh->pd->provider_name,
1192 0 : wh->applicant_id,
1193 0 : wh->verification_id,
1194 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1195 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1196 : NULL,
1197 : MHD_HTTP_BAD_GATEWAY,
1198 : resp);
1199 0 : break;
1200 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
1201 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1202 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1203 : response_code),
1204 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1205 : (json_t *) j));
1206 0 : wh->cb (wh->cb_cls,
1207 : wh->process_row,
1208 0 : &wh->h_payto,
1209 0 : wh->is_wallet,
1210 0 : wh->pd->provider_name,
1211 0 : wh->applicant_id,
1212 0 : wh->verification_id,
1213 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1214 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1215 : NULL,
1216 : MHD_HTTP_SERVICE_UNAVAILABLE,
1217 : resp);
1218 0 : break;
1219 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1220 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1221 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1222 : response_code),
1223 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1224 : (json_t *) j));
1225 0 : wh->cb (wh->cb_cls,
1226 : wh->process_row,
1227 0 : &wh->h_payto,
1228 0 : wh->is_wallet,
1229 0 : wh->pd->provider_name,
1230 0 : wh->applicant_id,
1231 0 : wh->verification_id,
1232 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1233 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1234 : NULL,
1235 : MHD_HTTP_BAD_GATEWAY,
1236 : resp);
1237 0 : break;
1238 0 : default:
1239 0 : resp = TALER_MHD_MAKE_JSON_PACK (
1240 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
1241 : response_code),
1242 : GNUNET_JSON_pack_object_incref ("kycaid_body",
1243 : (json_t *) j));
1244 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1245 : "Unexpected KYCAID response %u:\n",
1246 : (unsigned int) response_code);
1247 : #if DEBUG
1248 : json_dumpf (j,
1249 : stderr,
1250 : JSON_INDENT (2));
1251 : #endif
1252 0 : wh->cb (wh->cb_cls,
1253 : wh->process_row,
1254 0 : &wh->h_payto,
1255 0 : wh->is_wallet,
1256 0 : wh->pd->provider_name,
1257 0 : wh->applicant_id,
1258 0 : wh->verification_id,
1259 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1260 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1261 : NULL,
1262 : MHD_HTTP_BAD_GATEWAY,
1263 : resp);
1264 0 : break;
1265 : }
1266 0 : kycaid_webhook_cancel (wh);
1267 : }
1268 :
1269 :
1270 : /**
1271 : * Asynchronously return a reply for the webhook.
1272 : *
1273 : * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
1274 : */
1275 : static void
1276 0 : async_webhook_reply (void *cls)
1277 : {
1278 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1279 :
1280 0 : wh->task = NULL;
1281 0 : wh->cb (wh->cb_cls,
1282 : wh->process_row,
1283 0 : (0 == wh->process_row)
1284 : ? NULL
1285 : : &wh->h_payto,
1286 0 : wh->is_wallet,
1287 0 : wh->pd->provider_name,
1288 0 : wh->applicant_id, /* provider user ID */
1289 0 : wh->verification_id, /* provider legi ID */
1290 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1291 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1292 : NULL,
1293 : wh->response_code,
1294 : wh->resp);
1295 0 : kycaid_webhook_cancel (wh);
1296 0 : }
1297 :
1298 :
1299 : /**
1300 : * Check KYC status and return result for Webhook. We do NOT implement the
1301 : * authentication check proposed by the KYCAID documentation, as it would
1302 : * allow an attacker who learns the access token to easily bypass the KYC
1303 : * checks. Instead, we insist on explicitly requesting the KYC status from the
1304 : * provider (at least on success).
1305 : *
1306 : * @param cls the @e cls of this struct with the plugin-specific state
1307 : * @param pd provider configuration details
1308 : * @param plc callback to lookup accounts with
1309 : * @param plc_cls closure for @a plc
1310 : * @param http_method HTTP method used for the webhook
1311 : * @param url_path rest of the URL after `/kyc-webhook/`
1312 : * @param connection MHD connection object (for HTTP headers)
1313 : * @param body HTTP request body
1314 : * @param cb function to call with the result
1315 : * @param cb_cls closure for @a cb
1316 : * @return handle to cancel operation early
1317 : */
1318 : static struct TALER_KYCLOGIC_WebhookHandle *
1319 0 : kycaid_webhook (void *cls,
1320 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1321 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1322 : void *plc_cls,
1323 : const char *http_method,
1324 : const char *const url_path[],
1325 : struct MHD_Connection *connection,
1326 : const json_t *body,
1327 : TALER_KYCLOGIC_WebhookCallback cb,
1328 : void *cb_cls)
1329 : {
1330 0 : struct PluginState *ps = cls;
1331 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1332 : CURL *eh;
1333 : const char *request_id;
1334 : const char *type;
1335 : const char *verification_id; /* = provider_legitimization_id */
1336 : const char *applicant_id;
1337 : const char *form_id;
1338 0 : const char *status = NULL;
1339 0 : bool verified = false;
1340 0 : bool no_verified = true;
1341 0 : const json_t *verifications = NULL;
1342 : struct GNUNET_JSON_Specification spec[] = {
1343 0 : GNUNET_JSON_spec_string ("request_id",
1344 : &request_id),
1345 0 : GNUNET_JSON_spec_string ("type",
1346 : &type),
1347 0 : GNUNET_JSON_spec_string ("verification_id",
1348 : &verification_id),
1349 0 : GNUNET_JSON_spec_string ("applicant_id",
1350 : &applicant_id),
1351 0 : GNUNET_JSON_spec_string ("form_id",
1352 : &form_id),
1353 0 : GNUNET_JSON_spec_mark_optional (
1354 : GNUNET_JSON_spec_string ("status",
1355 : &status),
1356 : NULL),
1357 0 : GNUNET_JSON_spec_mark_optional (
1358 : GNUNET_JSON_spec_bool ("verified",
1359 : &verified),
1360 : &no_verified),
1361 0 : GNUNET_JSON_spec_mark_optional (
1362 : GNUNET_JSON_spec_object_const ("verifications",
1363 : &verifications),
1364 : NULL),
1365 0 : GNUNET_JSON_spec_end ()
1366 : };
1367 : enum GNUNET_DB_QueryStatus qs;
1368 :
1369 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1370 0 : wh->cb = cb;
1371 0 : wh->cb_cls = cb_cls;
1372 0 : wh->ps = ps;
1373 0 : wh->pd = pd;
1374 0 : wh->connection = connection;
1375 : #if DEBUG
1376 : if (NULL != body)
1377 : json_dumpf (body,
1378 : stderr,
1379 : JSON_INDENT (2));
1380 : #endif
1381 0 : if (NULL == pd)
1382 : {
1383 0 : GNUNET_break_op (0);
1384 : #if DEBUG
1385 : json_dumpf (body,
1386 : stderr,
1387 : JSON_INDENT (2));
1388 : #endif
1389 0 : wh->resp = TALER_MHD_make_error (
1390 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
1391 : "kycaid");
1392 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
1393 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1394 : wh);
1395 0 : return wh;
1396 : }
1397 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1398 : "KYCAID webhook of `%s' triggered with %s\n",
1399 : pd->section,
1400 : http_method);
1401 0 : if (GNUNET_OK !=
1402 0 : GNUNET_JSON_parse (body,
1403 : spec,
1404 : NULL, NULL))
1405 : {
1406 0 : GNUNET_break_op (0);
1407 : #if DEBUG
1408 : json_dumpf (body,
1409 : stderr,
1410 : JSON_INDENT (2));
1411 : #endif
1412 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1413 : GNUNET_JSON_pack_object_incref ("webhook_body",
1414 : (json_t *) body));
1415 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
1416 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1417 : wh);
1418 0 : return wh;
1419 : }
1420 0 : qs = plc (plc_cls,
1421 0 : pd->provider_name,
1422 : verification_id,
1423 : &wh->h_payto,
1424 : &wh->is_wallet,
1425 : &wh->process_row);
1426 0 : if (qs < 0)
1427 : {
1428 0 : wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
1429 : "provider-legitimization-lookup");
1430 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1431 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1432 : wh);
1433 0 : return wh;
1434 : }
1435 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1436 : {
1437 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1438 : "Received webhook for unknown verification ID `%s' and section `%s'\n",
1439 : verification_id,
1440 : pd->section);
1441 0 : wh->resp = TALER_MHD_make_error (
1442 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
1443 : verification_id);
1444 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
1445 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1446 : wh);
1447 0 : return wh;
1448 : }
1449 0 : wh->verification_id = GNUNET_strdup (verification_id);
1450 0 : wh->applicant_id = GNUNET_strdup (applicant_id);
1451 0 : if ( (0 != strcasecmp (type,
1452 0 : "VERIFICATION_COMPLETED")) ||
1453 0 : (no_verified) ||
1454 0 : (! verified) )
1455 : {
1456 : /* We don't need to re-confirm the failure by
1457 : asking the API again. */
1458 0 : log_failure (verifications);
1459 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1460 : "Webhook called with non-completion status: %s\n",
1461 : type);
1462 0 : wh->response_code = MHD_HTTP_NO_CONTENT;
1463 0 : wh->resp = MHD_create_response_from_buffer_static (0,
1464 : "");
1465 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1466 : wh);
1467 0 : return wh;
1468 : }
1469 :
1470 0 : eh = curl_easy_init ();
1471 0 : if (NULL == eh)
1472 : {
1473 0 : GNUNET_break (0);
1474 0 : wh->resp = TALER_MHD_make_error (
1475 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1476 : NULL);
1477 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1478 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1479 : wh);
1480 0 : return wh;
1481 : }
1482 :
1483 : {
1484 : char *applicant_id_encoded;
1485 :
1486 0 : applicant_id_encoded = TALER_urlencode (applicant_id);
1487 0 : GNUNET_asprintf (&wh->url,
1488 : "https://api.kycaid.com/applicants/%s",
1489 : applicant_id_encoded);
1490 0 : GNUNET_free (applicant_id_encoded);
1491 : }
1492 0 : GNUNET_break (CURLE_OK ==
1493 : curl_easy_setopt (eh,
1494 : CURLOPT_VERBOSE,
1495 : 0));
1496 0 : GNUNET_assert (CURLE_OK ==
1497 : curl_easy_setopt (eh,
1498 : CURLOPT_MAXREDIRS,
1499 : 1L));
1500 0 : GNUNET_break (CURLE_OK ==
1501 : curl_easy_setopt (eh,
1502 : CURLOPT_URL,
1503 : wh->url));
1504 0 : wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
1505 : eh,
1506 0 : pd->slist,
1507 : &handle_webhook_finished,
1508 : wh);
1509 0 : return wh;
1510 : }
1511 :
1512 :
1513 : /**
1514 : * Initialize kycaid logic plugin
1515 : *
1516 : * @param cls a configuration instance
1517 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
1518 : */
1519 : void *
1520 : libtaler_plugin_kyclogic_kycaid_init (void *cls);
1521 :
1522 : /* declaration to avoid compiler warning */
1523 : void *
1524 61 : libtaler_plugin_kyclogic_kycaid_init (void *cls)
1525 : {
1526 61 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
1527 : struct TALER_KYCLOGIC_Plugin *plugin;
1528 : struct PluginState *ps;
1529 :
1530 61 : ps = GNUNET_new (struct PluginState);
1531 61 : ps->cfg = cfg;
1532 61 : if (GNUNET_OK !=
1533 61 : GNUNET_CONFIGURATION_get_value_string (cfg,
1534 : "exchange",
1535 : "BASE_URL",
1536 : &ps->exchange_base_url))
1537 : {
1538 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1539 : "exchange",
1540 : "BASE_URL");
1541 0 : GNUNET_free (ps);
1542 0 : return NULL;
1543 : }
1544 :
1545 : ps->curl_ctx
1546 122 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1547 61 : &ps->curl_rc);
1548 61 : if (NULL == ps->curl_ctx)
1549 : {
1550 0 : GNUNET_break (0);
1551 0 : GNUNET_free (ps->exchange_base_url);
1552 0 : GNUNET_free (ps);
1553 0 : return NULL;
1554 : }
1555 61 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
1556 :
1557 61 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
1558 61 : plugin->cls = ps;
1559 : plugin->load_configuration
1560 61 : = &kycaid_load_configuration;
1561 : plugin->unload_configuration
1562 61 : = &kycaid_unload_configuration;
1563 : plugin->initiate
1564 61 : = &kycaid_initiate;
1565 : plugin->initiate_cancel
1566 61 : = &kycaid_initiate_cancel;
1567 : plugin->proof
1568 61 : = &kycaid_proof;
1569 : plugin->proof_cancel
1570 61 : = &kycaid_proof_cancel;
1571 : plugin->webhook
1572 61 : = &kycaid_webhook;
1573 : plugin->webhook_cancel
1574 61 : = &kycaid_webhook_cancel;
1575 61 : return plugin;
1576 : }
1577 :
1578 :
1579 : /**
1580 : * Unload authorization plugin
1581 : *
1582 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
1583 : * @return NULL (always)
1584 : */
1585 : void *
1586 : libtaler_plugin_kyclogic_kycaid_done (void *cls);
1587 :
1588 : /* declaration to avoid compiler warning */
1589 : void *
1590 61 : libtaler_plugin_kyclogic_kycaid_done (void *cls)
1591 : {
1592 61 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
1593 61 : struct PluginState *ps = plugin->cls;
1594 :
1595 61 : if (NULL != ps->curl_ctx)
1596 : {
1597 61 : GNUNET_CURL_fini (ps->curl_ctx);
1598 61 : ps->curl_ctx = NULL;
1599 : }
1600 61 : if (NULL != ps->curl_rc)
1601 : {
1602 61 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
1603 61 : ps->curl_rc = NULL;
1604 : }
1605 61 : GNUNET_free (ps->exchange_base_url);
1606 61 : GNUNET_free (ps);
1607 61 : GNUNET_free (plugin);
1608 61 : return NULL;
1609 : }
|