Line data Source code
1 : /*
2 : This file is part of GNU 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.GPL. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file plugin_kyclogic_persona.c
18 : * @brief persona for an authentication flow logic
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler_kyclogic_plugin.h"
23 : #include "taler_mhd_lib.h"
24 : #include "taler_curl_lib.h"
25 : #include "taler_json_lib.h"
26 : #include "taler_kyclogic_lib.h"
27 : #include "taler_templating_lib.h"
28 : #include <regex.h>
29 : #include "taler_util.h"
30 :
31 :
32 : /**
33 : * Which version of the persona API are we implementing?
34 : */
35 : #define PERSONA_VERSION "2021-07-05"
36 :
37 : /**
38 : * Saves the state of a plugin.
39 : */
40 : struct PluginState
41 : {
42 :
43 : /**
44 : * Our base URL.
45 : */
46 : char *exchange_base_url;
47 :
48 : /**
49 : * Our global configuration.
50 : */
51 : const struct GNUNET_CONFIGURATION_Handle *cfg;
52 :
53 : /**
54 : * Context for CURL operations (useful to the event loop)
55 : */
56 : struct GNUNET_CURL_Context *curl_ctx;
57 :
58 : /**
59 : * Context for integrating @e curl_ctx with the
60 : * GNUnet event loop.
61 : */
62 : struct GNUNET_CURL_RescheduleContext *curl_rc;
63 :
64 : /**
65 : * Authorization token to use when receiving webhooks from the Persona service. Optional. Note that
66 : * webhooks are *global* and not per template.
67 : */
68 : char *webhook_token;
69 :
70 :
71 : };
72 :
73 :
74 : /**
75 : * Keeps the plugin-specific state for
76 : * a given configuration section.
77 : */
78 : struct TALER_KYCLOGIC_ProviderDetails
79 : {
80 :
81 : /**
82 : * Overall plugin state.
83 : */
84 : struct PluginState *ps;
85 :
86 : /**
87 : * Configuration section that configured us.
88 : */
89 : char *section;
90 :
91 : /**
92 : * Salt to use for idempotency.
93 : */
94 : char *salt;
95 :
96 : /**
97 : * Authorization token to use when talking
98 : * to the service.
99 : */
100 : char *auth_token;
101 :
102 : /**
103 : * Template ID for the KYC check to perform.
104 : */
105 : char *template_id;
106 :
107 : /**
108 : * Subdomain to use.
109 : */
110 : char *subdomain;
111 :
112 : /**
113 : * Where to redirect the client upon completion.
114 : */
115 : char *post_kyc_redirect_url;
116 :
117 : /**
118 : * Validity time for a successful KYC process.
119 : */
120 : struct GNUNET_TIME_Relative validity;
121 :
122 : /**
123 : * Curl-ready authentication header to use.
124 : */
125 : struct curl_slist *slist;
126 :
127 : };
128 :
129 :
130 : /**
131 : * Handle for an initiation operation.
132 : */
133 : struct TALER_KYCLOGIC_InitiateHandle
134 : {
135 :
136 : /**
137 : * Hash of the payto:// URI we are initiating the KYC for.
138 : */
139 : struct TALER_PaytoHashP h_payto;
140 :
141 : /**
142 : * UUID being checked.
143 : */
144 : uint64_t legitimization_uuid;
145 :
146 : /**
147 : * Our configuration details.
148 : */
149 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
150 :
151 : /**
152 : * Continuation to call.
153 : */
154 : TALER_KYCLOGIC_InitiateCallback cb;
155 :
156 : /**
157 : * Closure for @a cb.
158 : */
159 : void *cb_cls;
160 :
161 : /**
162 : * Context for #TEH_curl_easy_post(). Keeps the data that must
163 : * persist for Curl to make the upload.
164 : */
165 : struct TALER_CURL_PostContext ctx;
166 :
167 : /**
168 : * Handle for the request.
169 : */
170 : struct GNUNET_CURL_Job *job;
171 :
172 : /**
173 : * URL of the cURL request.
174 : */
175 : char *url;
176 :
177 : /**
178 : * Request-specific headers to use.
179 : */
180 : struct curl_slist *slist;
181 :
182 : };
183 :
184 :
185 : /**
186 : * Handle for an KYC proof operation.
187 : */
188 : struct TALER_KYCLOGIC_ProofHandle
189 : {
190 :
191 : /**
192 : * Overall plugin state.
193 : */
194 : struct PluginState *ps;
195 :
196 : /**
197 : * Our configuration details.
198 : */
199 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
200 :
201 : /**
202 : * Continuation to call.
203 : */
204 : TALER_KYCLOGIC_ProofCallback cb;
205 :
206 : /**
207 : * Closure for @e cb.
208 : */
209 : void *cb_cls;
210 :
211 : /**
212 : * Connection we are handling.
213 : */
214 : struct MHD_Connection *connection;
215 :
216 : /**
217 : * Task for asynchronous execution.
218 : */
219 : struct GNUNET_SCHEDULER_Task *task;
220 :
221 : /**
222 : * Handle for the request.
223 : */
224 : struct GNUNET_CURL_Job *job;
225 :
226 : /**
227 : * URL of the cURL request.
228 : */
229 : char *url;
230 :
231 : /**
232 : * Hash of the payto:// URI we are checking the KYC for.
233 : */
234 : struct TALER_PaytoHashP h_payto;
235 :
236 : /**
237 : * Row in the legitimization processes of the
238 : * legitimization proof that is being checked.
239 : */
240 : uint64_t process_row;
241 :
242 : /**
243 : * Account ID at the provider.
244 : */
245 : char *provider_user_id;
246 :
247 : /**
248 : * Inquiry ID at the provider.
249 : */
250 : char *inquiry_id;
251 : };
252 :
253 :
254 : /**
255 : * Handle for an KYC Web hook operation.
256 : */
257 : struct TALER_KYCLOGIC_WebhookHandle
258 : {
259 :
260 : /**
261 : * Continuation to call when done.
262 : */
263 : TALER_KYCLOGIC_WebhookCallback cb;
264 :
265 : /**
266 : * Closure for @a cb.
267 : */
268 : void *cb_cls;
269 :
270 : /**
271 : * Task for asynchronous execution.
272 : */
273 : struct GNUNET_SCHEDULER_Task *task;
274 :
275 : /**
276 : * Overall plugin state.
277 : */
278 : struct PluginState *ps;
279 :
280 : /**
281 : * Our configuration details.
282 : */
283 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
284 :
285 : /**
286 : * Connection we are handling.
287 : */
288 : struct MHD_Connection *connection;
289 :
290 : /**
291 : * Verification ID from the service.
292 : */
293 : char *inquiry_id;
294 :
295 : /**
296 : * URL of the cURL request.
297 : */
298 : char *url;
299 :
300 : /**
301 : * Handle for the request.
302 : */
303 : struct GNUNET_CURL_Job *job;
304 :
305 : /**
306 : * Response to return asynchronously.
307 : */
308 : struct MHD_Response *resp;
309 :
310 : /**
311 : * ID of the template the webhook is about,
312 : * according to the service.
313 : */
314 : const char *template_id;
315 :
316 : /**
317 : * Our account ID.
318 : */
319 : struct TALER_PaytoHashP h_payto;
320 :
321 : /**
322 : * UUID being checked.
323 : */
324 : uint64_t process_row;
325 :
326 : /**
327 : * HTTP response code to return asynchronously.
328 : */
329 : unsigned int response_code;
330 : };
331 :
332 :
333 : /**
334 : * Release configuration resources previously loaded
335 : *
336 : * @param[in] pd configuration to release
337 : */
338 : static void
339 0 : persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
340 : {
341 0 : curl_slist_free_all (pd->slist);
342 0 : GNUNET_free (pd->auth_token);
343 0 : GNUNET_free (pd->template_id);
344 0 : GNUNET_free (pd->subdomain);
345 0 : GNUNET_free (pd->salt);
346 0 : GNUNET_free (pd->section);
347 0 : GNUNET_free (pd->post_kyc_redirect_url);
348 0 : GNUNET_free (pd);
349 0 : }
350 :
351 :
352 : /**
353 : * Load the configuration of the KYC provider.
354 : *
355 : * @param cls closure
356 : * @param provider_section_name configuration section to parse
357 : * @return NULL if configuration is invalid
358 : */
359 : static struct TALER_KYCLOGIC_ProviderDetails *
360 1 : persona_load_configuration (void *cls,
361 : const char *provider_section_name)
362 : {
363 1 : struct PluginState *ps = cls;
364 : struct TALER_KYCLOGIC_ProviderDetails *pd;
365 :
366 1 : pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
367 1 : pd->ps = ps;
368 1 : pd->section = GNUNET_strdup (provider_section_name);
369 1 : if (GNUNET_OK !=
370 1 : GNUNET_CONFIGURATION_get_value_time (ps->cfg,
371 : provider_section_name,
372 : "PERSONA_VALIDITY",
373 : &pd->validity))
374 : {
375 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
376 : provider_section_name,
377 : "PERSONA_VALIDITY");
378 0 : persona_unload_configuration (pd);
379 0 : return NULL;
380 : }
381 1 : if (GNUNET_OK !=
382 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
383 : provider_section_name,
384 : "PERSONA_AUTH_TOKEN",
385 : &pd->auth_token))
386 : {
387 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
388 : provider_section_name,
389 : "PERSONA_AUTH_TOKEN");
390 0 : persona_unload_configuration (pd);
391 0 : return NULL;
392 : }
393 1 : if (GNUNET_OK !=
394 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
395 : provider_section_name,
396 : "SALT",
397 : &pd->salt))
398 : {
399 : uint32_t salt[8];
400 :
401 1 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
402 : salt,
403 : sizeof (salt));
404 1 : pd->salt = GNUNET_STRINGS_data_to_string_alloc (salt,
405 : sizeof (salt));
406 : }
407 1 : if (GNUNET_OK !=
408 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
409 : provider_section_name,
410 : "PERSONA_SUBDOMAIN",
411 : &pd->subdomain))
412 : {
413 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
414 : provider_section_name,
415 : "PERSONA_SUBDOMAIN");
416 0 : persona_unload_configuration (pd);
417 0 : return NULL;
418 : }
419 1 : if (GNUNET_OK !=
420 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
421 : provider_section_name,
422 : "KYC_POST_URL",
423 : &pd->post_kyc_redirect_url))
424 : {
425 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
426 : provider_section_name,
427 : "KYC_POST_URL");
428 0 : persona_unload_configuration (pd);
429 0 : return NULL;
430 : }
431 1 : if (GNUNET_OK !=
432 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
433 : provider_section_name,
434 : "PERSONA_TEMPLATE_ID",
435 : &pd->template_id))
436 : {
437 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
438 : provider_section_name,
439 : "PERSONA_TEMPLATE_ID");
440 0 : persona_unload_configuration (pd);
441 0 : return NULL;
442 : }
443 : {
444 : char *auth;
445 :
446 1 : GNUNET_asprintf (&auth,
447 : "%s: Bearer %s",
448 : MHD_HTTP_HEADER_AUTHORIZATION,
449 : pd->auth_token);
450 1 : pd->slist = curl_slist_append (NULL,
451 : auth);
452 1 : GNUNET_free (auth);
453 1 : GNUNET_asprintf (&auth,
454 : "%s: %s",
455 : MHD_HTTP_HEADER_ACCEPT,
456 : "application/json");
457 1 : pd->slist = curl_slist_append (pd->slist,
458 : "Persona-Version: "
459 : PERSONA_VERSION);
460 1 : GNUNET_free (auth);
461 : }
462 1 : return pd;
463 : }
464 :
465 :
466 : /**
467 : * Cancel KYC check initiation.
468 : *
469 : * @param[in] ih handle of operation to cancel
470 : */
471 : static void
472 0 : persona_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
473 : {
474 0 : if (NULL != ih->job)
475 : {
476 0 : GNUNET_CURL_job_cancel (ih->job);
477 0 : ih->job = NULL;
478 : }
479 0 : GNUNET_free (ih->url);
480 0 : TALER_curl_easy_post_finished (&ih->ctx);
481 0 : curl_slist_free_all (ih->slist);
482 0 : GNUNET_free (ih);
483 0 : }
484 :
485 :
486 : /**
487 : * Function called when we're done processing the
488 : * HTTP POST "/api/v1/inquiries" request.
489 : *
490 : * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
491 : * @param response_code HTTP response code, 0 on error
492 : * @param response parsed JSON result, NULL on error
493 : */
494 : static void
495 0 : handle_initiate_finished (void *cls,
496 : long response_code,
497 : const void *response)
498 : {
499 0 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
500 0 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
501 0 : const json_t *j = response;
502 : char *url;
503 : json_t *data;
504 : const char *type;
505 : const char *inquiry_id;
506 : const char *persona_account_id;
507 : const char *ename;
508 : unsigned int eline;
509 : struct GNUNET_JSON_Specification spec[] = {
510 0 : GNUNET_JSON_spec_string ("type",
511 : &type),
512 0 : GNUNET_JSON_spec_string ("id",
513 : &inquiry_id),
514 0 : GNUNET_JSON_spec_end ()
515 : };
516 :
517 0 : ih->job = NULL;
518 0 : switch (response_code)
519 : {
520 0 : case MHD_HTTP_CREATED:
521 : /* handled below */
522 0 : break;
523 0 : case MHD_HTTP_UNAUTHORIZED:
524 : case MHD_HTTP_FORBIDDEN:
525 : {
526 : const char *msg;
527 :
528 0 : msg = json_string_value (
529 0 : json_object_get (
530 0 : json_array_get (
531 0 : json_object_get (j,
532 : "errors"),
533 : 0),
534 : "title"));
535 :
536 0 : ih->cb (ih->cb_cls,
537 : TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
538 : NULL,
539 : NULL,
540 : NULL,
541 : msg);
542 0 : persona_initiate_cancel (ih);
543 0 : return;
544 : }
545 0 : case MHD_HTTP_NOT_FOUND:
546 : case MHD_HTTP_CONFLICT:
547 : {
548 : const char *msg;
549 :
550 0 : msg = json_string_value (
551 0 : json_object_get (
552 0 : json_array_get (
553 0 : json_object_get (j,
554 : "errors"),
555 : 0),
556 : "title"));
557 :
558 0 : ih->cb (ih->cb_cls,
559 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
560 : NULL,
561 : NULL,
562 : NULL,
563 : msg);
564 0 : persona_initiate_cancel (ih);
565 0 : return;
566 : }
567 0 : case MHD_HTTP_BAD_REQUEST:
568 : case MHD_HTTP_UNPROCESSABLE_ENTITY:
569 : {
570 : const char *msg;
571 :
572 0 : GNUNET_break (0);
573 0 : json_dumpf (j,
574 : stderr,
575 : JSON_INDENT (2));
576 0 : msg = json_string_value (
577 0 : json_object_get (
578 0 : json_array_get (
579 0 : json_object_get (j,
580 : "errors"),
581 : 0),
582 : "title"));
583 :
584 0 : ih->cb (ih->cb_cls,
585 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
586 : NULL,
587 : NULL,
588 : NULL,
589 : msg);
590 0 : persona_initiate_cancel (ih);
591 0 : return;
592 : }
593 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
594 : {
595 : const char *msg;
596 :
597 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
598 : "Rate limiting requested:\n");
599 0 : json_dumpf (j,
600 : stderr,
601 : JSON_INDENT (2));
602 0 : msg = json_string_value (
603 0 : json_object_get (
604 0 : json_array_get (
605 0 : json_object_get (j,
606 : "errors"),
607 : 0),
608 : "title"));
609 0 : ih->cb (ih->cb_cls,
610 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
611 : NULL,
612 : NULL,
613 : NULL,
614 : msg);
615 0 : persona_initiate_cancel (ih);
616 0 : return;
617 : }
618 0 : default:
619 : {
620 : char *err;
621 :
622 0 : GNUNET_break_op (0);
623 0 : json_dumpf (j,
624 : stderr,
625 : JSON_INDENT (2));
626 0 : GNUNET_asprintf (&err,
627 : "Unexpected HTTP status %u from Persona\n",
628 : (unsigned int) response_code);
629 0 : ih->cb (ih->cb_cls,
630 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
631 : NULL,
632 : NULL,
633 : NULL,
634 : err);
635 0 : GNUNET_free (err);
636 0 : persona_initiate_cancel (ih);
637 0 : return;
638 : }
639 : }
640 0 : data = json_object_get (j,
641 : "data");
642 0 : if (NULL == data)
643 : {
644 0 : GNUNET_break_op (0);
645 0 : json_dumpf (j,
646 : stderr,
647 : JSON_INDENT (2));
648 0 : persona_initiate_cancel (ih);
649 0 : return;
650 : }
651 :
652 0 : if (GNUNET_OK !=
653 0 : GNUNET_JSON_parse (data,
654 : spec,
655 : &ename,
656 : &eline))
657 : {
658 0 : GNUNET_break_op (0);
659 0 : json_dumpf (j,
660 : stderr,
661 : JSON_INDENT (2));
662 0 : ih->cb (ih->cb_cls,
663 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
664 : NULL,
665 : NULL,
666 : NULL,
667 : ename);
668 0 : persona_initiate_cancel (ih);
669 0 : return;
670 : }
671 : persona_account_id
672 0 : = json_string_value (
673 0 : json_object_get (
674 0 : json_object_get (
675 0 : json_object_get (
676 0 : json_object_get (data,
677 : "relationships"),
678 : "account"),
679 : "data"),
680 : "id"));
681 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
682 : "Starting inquiry %s for Persona account %s\n",
683 : inquiry_id,
684 : persona_account_id);
685 0 : GNUNET_asprintf (&url,
686 : "https://%s.withpersona.com/verify"
687 : "?inquiry-id=%s",
688 : pd->subdomain,
689 : inquiry_id);
690 0 : ih->cb (ih->cb_cls,
691 : TALER_EC_NONE,
692 : url,
693 : persona_account_id,
694 : inquiry_id,
695 : NULL);
696 0 : GNUNET_free (url);
697 0 : persona_initiate_cancel (ih);
698 : }
699 :
700 :
701 : /**
702 : * Initiate KYC check.
703 : *
704 : * @param cls the @e cls of this struct with the plugin-specific state
705 : * @param pd provider configuration details
706 : * @param account_id which account to trigger process for
707 : * @param legitimization_uuid unique ID for the legitimization process
708 : * @param cb function to call with the result
709 : * @param cb_cls closure for @a cb
710 : * @return handle to cancel operation early
711 : */
712 : static struct TALER_KYCLOGIC_InitiateHandle *
713 0 : persona_initiate (void *cls,
714 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
715 : const struct TALER_PaytoHashP *account_id,
716 : uint64_t legitimization_uuid,
717 : TALER_KYCLOGIC_InitiateCallback cb,
718 : void *cb_cls)
719 : {
720 0 : struct PluginState *ps = cls;
721 : struct TALER_KYCLOGIC_InitiateHandle *ih;
722 : json_t *body;
723 : CURL *eh;
724 :
725 0 : eh = curl_easy_init ();
726 0 : if (NULL == eh)
727 : {
728 0 : GNUNET_break (0);
729 0 : return NULL;
730 : }
731 0 : ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
732 0 : ih->legitimization_uuid = legitimization_uuid;
733 0 : ih->cb = cb;
734 0 : ih->cb_cls = cb_cls;
735 0 : ih->h_payto = *account_id;
736 0 : ih->pd = pd;
737 0 : GNUNET_asprintf (&ih->url,
738 : "https://withpersona.com/api/v1/inquiries");
739 : {
740 : char *payto_s;
741 : char *proof_url;
742 : char ref_s[24];
743 :
744 0 : GNUNET_snprintf (ref_s,
745 : sizeof (ref_s),
746 : "%llu",
747 0 : (unsigned long long) ih->legitimization_uuid);
748 0 : payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
749 : sizeof (ih->h_payto));
750 : /* NOTE: check here that exchange_base_url ends
751 : with a '/'? */
752 0 : GNUNET_asprintf (&proof_url,
753 : "%skyc-proof/%s/%s",
754 0 : pd->ps->exchange_base_url,
755 : payto_s,
756 : pd->section);
757 0 : body = GNUNET_JSON_PACK (
758 : GNUNET_JSON_pack_object_steal (
759 : "data",
760 : GNUNET_JSON_PACK (
761 : GNUNET_JSON_pack_object_steal (
762 : "attributes",
763 : GNUNET_JSON_PACK (
764 : GNUNET_JSON_pack_string ("inquiry_template_id",
765 : pd->template_id),
766 : GNUNET_JSON_pack_string ("reference_id",
767 : ref_s),
768 : GNUNET_JSON_pack_string ("redirect_uri",
769 : proof_url)
770 : )))));
771 0 : GNUNET_assert (NULL != body);
772 0 : GNUNET_free (payto_s);
773 0 : GNUNET_free (proof_url);
774 : }
775 0 : GNUNET_break (CURLE_OK ==
776 : curl_easy_setopt (eh,
777 : CURLOPT_VERBOSE,
778 : 0));
779 0 : GNUNET_assert (CURLE_OK ==
780 : curl_easy_setopt (eh,
781 : CURLOPT_MAXREDIRS,
782 : 1L));
783 0 : GNUNET_break (CURLE_OK ==
784 : curl_easy_setopt (eh,
785 : CURLOPT_URL,
786 : ih->url));
787 0 : ih->ctx.disable_compression = true;
788 0 : if (GNUNET_OK !=
789 0 : TALER_curl_easy_post (&ih->ctx,
790 : eh,
791 : body))
792 : {
793 0 : GNUNET_break (0);
794 0 : GNUNET_free (ih->url);
795 0 : GNUNET_free (ih);
796 0 : curl_easy_cleanup (eh);
797 0 : json_decref (body);
798 0 : return NULL;
799 : }
800 0 : json_decref (body);
801 0 : ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
802 : eh,
803 0 : ih->ctx.headers,
804 : &handle_initiate_finished,
805 : ih);
806 0 : GNUNET_CURL_extend_headers (ih->job,
807 0 : pd->slist);
808 : {
809 : char *ikh;
810 :
811 0 : GNUNET_asprintf (&ikh,
812 : "Idempotency-Key: %llu-%s",
813 0 : (unsigned long long) ih->legitimization_uuid,
814 : pd->salt);
815 0 : ih->slist = curl_slist_append (NULL,
816 : ikh);
817 0 : GNUNET_free (ikh);
818 : }
819 0 : GNUNET_CURL_extend_headers (ih->job,
820 0 : ih->slist);
821 0 : return ih;
822 : }
823 :
824 :
825 : /**
826 : * Cancel KYC proof.
827 : *
828 : * @param[in] ph handle of operation to cancel
829 : */
830 : static void
831 0 : persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
832 : {
833 0 : if (NULL != ph->job)
834 : {
835 0 : GNUNET_CURL_job_cancel (ph->job);
836 0 : ph->job = NULL;
837 : }
838 0 : GNUNET_free (ph->url);
839 0 : GNUNET_free (ph->provider_user_id);
840 0 : GNUNET_free (ph->inquiry_id);
841 0 : GNUNET_free (ph);
842 0 : }
843 :
844 :
845 : /**
846 : * Call @a ph callback with the operation result.
847 : *
848 : * @param ph proof handle to generate reply for
849 : * @param status status to return
850 : * @param account_id account to return
851 : * @param inquiry_id inquiry ID to supply
852 : * @param http_status HTTP status to use
853 : * @param template template to instantiate
854 : * @param[in] body body for the template to use (reference
855 : * is consumed)
856 : */
857 : static void
858 0 : proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
859 : enum TALER_KYCLOGIC_KycStatus status,
860 : const char *account_id,
861 : const char *inquiry_id,
862 : unsigned int http_status,
863 : const char *template,
864 : json_t *body)
865 : {
866 : struct MHD_Response *resp;
867 : enum GNUNET_GenericReturnValue ret;
868 : struct GNUNET_TIME_Absolute expiration;
869 :
870 0 : if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
871 0 : expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
872 : else
873 0 : expiration = GNUNET_TIME_UNIT_ZERO_ABS;
874 0 : ret = TALER_TEMPLATING_build (ph->connection,
875 : &http_status,
876 : template,
877 : NULL,
878 : NULL,
879 : body,
880 : &resp);
881 0 : json_decref (body);
882 0 : if (GNUNET_SYSERR == ret)
883 : {
884 0 : GNUNET_break (0);
885 0 : resp = NULL; /* good luck */
886 : }
887 0 : ph->cb (ph->cb_cls,
888 : status,
889 : account_id,
890 : inquiry_id,
891 : expiration,
892 : http_status,
893 : resp);
894 0 : }
895 :
896 :
897 : /**
898 : * Call @a ph callback with HTTP error response.
899 : *
900 : * @param ph proof handle to generate reply for
901 : * @param inquiry_id inquiry ID to supply
902 : * @param http_status HTTP status to use
903 : * @param template template to instantiate
904 : * @param[in] body body for the template to use (reference
905 : * is consumed)
906 : */
907 : static void
908 0 : proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
909 : const char *inquiry_id,
910 : unsigned int http_status,
911 : const char *template,
912 : json_t *body)
913 : {
914 0 : proof_generic_reply (ph,
915 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
916 : NULL, /* user id */
917 : inquiry_id,
918 : http_status,
919 : template,
920 : body);
921 0 : }
922 :
923 :
924 : /**
925 : * Function called when we're done processing the
926 : * HTTP "/api/v1/verifications/{verification-id}" request.
927 : *
928 : * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
929 : * @param response_code HTTP response code, 0 on error
930 : * @param response parsed JSON result, NULL on error
931 : */
932 : static void
933 0 : handle_proof_finished (void *cls,
934 : long response_code,
935 : const void *response)
936 : {
937 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
938 0 : const json_t *j = response;
939 0 : const json_t *data = json_object_get (j,
940 : "data");
941 :
942 0 : ph->job = NULL;
943 0 : switch (response_code)
944 : {
945 0 : case MHD_HTTP_OK:
946 : {
947 : const char *inquiry_id;
948 : const char *account_id;
949 0 : const char *type = NULL;
950 : json_t *attributes;
951 : struct GNUNET_JSON_Specification spec[] = {
952 0 : GNUNET_JSON_spec_string ("type",
953 : &type),
954 0 : GNUNET_JSON_spec_string ("id",
955 : &inquiry_id),
956 0 : GNUNET_JSON_spec_json ("attributes",
957 : &attributes),
958 0 : GNUNET_JSON_spec_end ()
959 : };
960 :
961 0 : if ( (NULL == data) ||
962 : (GNUNET_OK !=
963 0 : GNUNET_JSON_parse (data,
964 : spec,
965 0 : NULL, NULL)) ||
966 0 : (0 != strcmp (type,
967 : "inquiry")) )
968 : {
969 0 : GNUNET_break_op (0);
970 0 : json_dumpf (j,
971 : stderr,
972 : JSON_INDENT (2));
973 0 : proof_reply_error (ph,
974 : inquiry_id,
975 : MHD_HTTP_BAD_GATEWAY,
976 : "persona-logic-failure",
977 0 : GNUNET_JSON_PACK (
978 : GNUNET_JSON_pack_uint64 ("persona_http_status",
979 : response_code),
980 : GNUNET_JSON_pack_string ("persona_inquiry_id",
981 : inquiry_id),
982 : TALER_JSON_pack_ec (
983 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
984 : GNUNET_JSON_pack_string ("detail",
985 : "data"),
986 : GNUNET_JSON_pack_allow_null (
987 : GNUNET_JSON_pack_object_incref ("data",
988 : (json_t *) data))));
989 0 : break;
990 : }
991 :
992 : {
993 : const char *status; /* "completed", what else? */
994 : const char *reference_id; /* or legitimization number */
995 0 : const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
996 : struct GNUNET_JSON_Specification ispec[] = {
997 0 : GNUNET_JSON_spec_string ("status",
998 : &status),
999 0 : GNUNET_JSON_spec_string ("reference_id",
1000 : &reference_id),
1001 0 : GNUNET_JSON_spec_mark_optional (
1002 : GNUNET_JSON_spec_string ("expired_at",
1003 : &expired_at),
1004 : NULL),
1005 0 : GNUNET_JSON_spec_end ()
1006 : };
1007 :
1008 0 : if (GNUNET_OK !=
1009 0 : GNUNET_JSON_parse (attributes,
1010 : ispec,
1011 : NULL, NULL))
1012 : {
1013 0 : GNUNET_break_op (0);
1014 0 : json_dumpf (j,
1015 : stderr,
1016 : JSON_INDENT (2));
1017 0 : proof_reply_error (ph,
1018 : inquiry_id,
1019 : MHD_HTTP_BAD_GATEWAY,
1020 : "persona-invalid-response",
1021 0 : GNUNET_JSON_PACK (
1022 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1023 : response_code),
1024 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1025 : inquiry_id),
1026 : TALER_JSON_pack_ec (
1027 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1028 : GNUNET_JSON_pack_string ("detail",
1029 : "data-attributes"),
1030 : GNUNET_JSON_pack_allow_null (
1031 : GNUNET_JSON_pack_object_incref ("data",
1032 : (json_t *) data))));
1033 0 : GNUNET_JSON_parse_free (ispec);
1034 0 : GNUNET_JSON_parse_free (spec);
1035 0 : break;
1036 : }
1037 : {
1038 : unsigned long long idr;
1039 : char dummy;
1040 :
1041 0 : if ( (1 != sscanf (reference_id,
1042 : "%llu%c",
1043 : &idr,
1044 0 : &dummy)) ||
1045 0 : (idr != ph->process_row) )
1046 : {
1047 0 : GNUNET_break_op (0);
1048 0 : proof_reply_error (ph,
1049 : inquiry_id,
1050 : MHD_HTTP_BAD_GATEWAY,
1051 : "persona-invalid-response",
1052 0 : GNUNET_JSON_PACK (
1053 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1054 : response_code),
1055 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1056 : inquiry_id),
1057 : TALER_JSON_pack_ec (
1058 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1059 : GNUNET_JSON_pack_string ("detail",
1060 : "data-attributes-reference_id"),
1061 : GNUNET_JSON_pack_allow_null (
1062 : GNUNET_JSON_pack_object_incref ("data",
1063 : (json_t *)
1064 : data))));
1065 0 : GNUNET_JSON_parse_free (ispec);
1066 0 : GNUNET_JSON_parse_free (spec);
1067 0 : break;
1068 : }
1069 : }
1070 :
1071 0 : if (0 != strcmp (inquiry_id,
1072 0 : ph->inquiry_id))
1073 : {
1074 0 : GNUNET_break_op (0);
1075 0 : proof_reply_error (ph,
1076 : inquiry_id,
1077 : MHD_HTTP_BAD_GATEWAY,
1078 : "persona-invalid-response",
1079 0 : GNUNET_JSON_PACK (
1080 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1081 : response_code),
1082 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1083 : inquiry_id),
1084 : TALER_JSON_pack_ec (
1085 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1086 : GNUNET_JSON_pack_string ("detail",
1087 : "data-id"),
1088 : GNUNET_JSON_pack_allow_null (
1089 : GNUNET_JSON_pack_object_incref ("data",
1090 : (json_t *)
1091 : data))));
1092 0 : GNUNET_JSON_parse_free (ispec);
1093 0 : GNUNET_JSON_parse_free (spec);
1094 0 : break;
1095 : }
1096 :
1097 0 : account_id = json_string_value (
1098 0 : json_object_get (
1099 0 : json_object_get (
1100 0 : json_object_get (
1101 0 : json_object_get (
1102 : data,
1103 : "relationships"),
1104 : "account"),
1105 : "data"),
1106 : "id"));
1107 :
1108 0 : if (0 != strcmp (status,
1109 : "completed"))
1110 : {
1111 0 : proof_generic_reply (ph,
1112 : TALER_KYCLOGIC_STATUS_FAILED,
1113 : account_id,
1114 : inquiry_id,
1115 : MHD_HTTP_OK,
1116 : "persona-kyc-failed",
1117 0 : GNUNET_JSON_PACK (
1118 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1119 : response_code),
1120 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1121 : inquiry_id),
1122 : GNUNET_JSON_pack_allow_null (
1123 : GNUNET_JSON_pack_object_incref ("data",
1124 : (json_t *)
1125 : data))));
1126 0 : GNUNET_JSON_parse_free (ispec);
1127 0 : GNUNET_JSON_parse_free (spec);
1128 0 : break;
1129 : }
1130 :
1131 0 : if (NULL == account_id)
1132 : {
1133 0 : GNUNET_break_op (0);
1134 0 : json_dumpf (data,
1135 : stderr,
1136 : JSON_INDENT (2));
1137 0 : proof_reply_error (ph,
1138 : inquiry_id,
1139 : MHD_HTTP_BAD_GATEWAY,
1140 : "persona-invalid-response",
1141 0 : GNUNET_JSON_PACK (
1142 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1143 : response_code),
1144 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1145 : inquiry_id),
1146 : TALER_JSON_pack_ec (
1147 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1148 : GNUNET_JSON_pack_string ("detail",
1149 : "data-relationships-account-data-id"),
1150 : GNUNET_JSON_pack_allow_null (
1151 : GNUNET_JSON_pack_object_incref ("data",
1152 : (json_t *)
1153 : data))));
1154 0 : break;
1155 : }
1156 :
1157 : {
1158 : struct MHD_Response *resp;
1159 : struct GNUNET_TIME_Absolute expiration;
1160 :
1161 0 : expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
1162 0 : resp = MHD_create_response_from_buffer (0,
1163 : "",
1164 : MHD_RESPMEM_PERSISTENT);
1165 0 : GNUNET_break (MHD_YES ==
1166 : MHD_add_response_header (resp,
1167 : MHD_HTTP_HEADER_LOCATION,
1168 : ph->pd->post_kyc_redirect_url));
1169 0 : TALER_MHD_add_global_headers (resp);
1170 0 : ph->cb (ph->cb_cls,
1171 : TALER_KYCLOGIC_STATUS_SUCCESS,
1172 : account_id,
1173 : inquiry_id,
1174 : expiration,
1175 : MHD_HTTP_SEE_OTHER,
1176 : resp);
1177 : }
1178 0 : GNUNET_JSON_parse_free (ispec);
1179 : }
1180 0 : GNUNET_JSON_parse_free (spec);
1181 0 : break;
1182 : }
1183 0 : case MHD_HTTP_BAD_REQUEST:
1184 : case MHD_HTTP_NOT_FOUND:
1185 : case MHD_HTTP_CONFLICT:
1186 : case MHD_HTTP_UNPROCESSABLE_ENTITY:
1187 : /* These are errors with this code */
1188 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1189 : "PERSONA failed with response %u:\n",
1190 : (unsigned int) response_code);
1191 0 : json_dumpf (j,
1192 : stderr,
1193 : JSON_INDENT (2));
1194 0 : proof_reply_error (ph,
1195 0 : ph->inquiry_id,
1196 : MHD_HTTP_BAD_GATEWAY,
1197 : "persona-logic-failure",
1198 0 : GNUNET_JSON_PACK (
1199 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1200 : response_code),
1201 : TALER_JSON_pack_ec (
1202 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1203 :
1204 : GNUNET_JSON_pack_allow_null (
1205 : GNUNET_JSON_pack_object_incref ("data",
1206 : (json_t *)
1207 : data))));
1208 0 : break;
1209 0 : case MHD_HTTP_UNAUTHORIZED:
1210 : /* These are failures of the exchange operator */
1211 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1212 : "Refused access with HTTP status code %u\n",
1213 : (unsigned int) response_code);
1214 0 : proof_reply_error (ph,
1215 0 : ph->inquiry_id,
1216 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1217 : "persona-exchange-unauthorized",
1218 0 : GNUNET_JSON_PACK (
1219 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1220 : response_code),
1221 : TALER_JSON_pack_ec (
1222 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
1223 : GNUNET_JSON_pack_allow_null (
1224 : GNUNET_JSON_pack_object_incref ("data",
1225 : (json_t *)
1226 : data))));
1227 0 : break;
1228 0 : case MHD_HTTP_PAYMENT_REQUIRED:
1229 : /* These are failures of the exchange operator */
1230 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1231 : "Refused access with HTTP status code %u\n",
1232 : (unsigned int) response_code);
1233 :
1234 0 : proof_reply_error (ph,
1235 0 : ph->inquiry_id,
1236 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1237 : "persona-exchange-unpaid",
1238 0 : GNUNET_JSON_PACK (
1239 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1240 : response_code),
1241 : TALER_JSON_pack_ec (
1242 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
1243 : GNUNET_JSON_pack_allow_null (
1244 : GNUNET_JSON_pack_object_incref ("data",
1245 : (json_t *)
1246 : data))));
1247 0 : break;
1248 0 : case MHD_HTTP_REQUEST_TIMEOUT:
1249 : /* These are networking issues */
1250 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1251 : "PERSONA failed with response %u:\n",
1252 : (unsigned int) response_code);
1253 0 : json_dumpf (j,
1254 : stderr,
1255 : JSON_INDENT (2));
1256 0 : proof_reply_error (ph,
1257 0 : ph->inquiry_id,
1258 : MHD_HTTP_GATEWAY_TIMEOUT,
1259 : "persona-network-timeout",
1260 0 : GNUNET_JSON_PACK (
1261 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1262 : response_code),
1263 : TALER_JSON_pack_ec (
1264 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
1265 : GNUNET_JSON_pack_allow_null (
1266 : GNUNET_JSON_pack_object_incref ("data",
1267 : (json_t *)
1268 : data))));
1269 0 : break;
1270 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
1271 : /* This is a load issue */
1272 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1273 : "PERSONA failed with response %u:\n",
1274 : (unsigned int) response_code);
1275 0 : json_dumpf (j,
1276 : stderr,
1277 : JSON_INDENT (2));
1278 0 : proof_reply_error (ph,
1279 0 : ph->inquiry_id,
1280 : MHD_HTTP_SERVICE_UNAVAILABLE,
1281 : "persona-load-failure",
1282 0 : GNUNET_JSON_PACK (
1283 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1284 : response_code),
1285 : TALER_JSON_pack_ec (
1286 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
1287 : GNUNET_JSON_pack_allow_null (
1288 : GNUNET_JSON_pack_object_incref ("data",
1289 : (json_t *)
1290 : data))));
1291 0 : break;
1292 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1293 : /* This is an issue with Persona */
1294 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1295 : "PERSONA failed with response %u:\n",
1296 : (unsigned int) response_code);
1297 0 : json_dumpf (j,
1298 : stderr,
1299 : JSON_INDENT (2));
1300 0 : proof_reply_error (ph,
1301 0 : ph->inquiry_id,
1302 : MHD_HTTP_BAD_GATEWAY,
1303 : "persona-provider-failure",
1304 0 : GNUNET_JSON_PACK (
1305 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1306 : response_code),
1307 : TALER_JSON_pack_ec (
1308 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
1309 : GNUNET_JSON_pack_allow_null (
1310 : GNUNET_JSON_pack_object_incref ("data",
1311 : (json_t *)
1312 : data))));
1313 0 : break;
1314 0 : default:
1315 : /* This is an issue with Persona */
1316 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1317 : "PERSONA failed with response %u:\n",
1318 : (unsigned int) response_code);
1319 0 : json_dumpf (j,
1320 : stderr,
1321 : JSON_INDENT (2));
1322 0 : proof_reply_error (ph,
1323 0 : ph->inquiry_id,
1324 : MHD_HTTP_BAD_GATEWAY,
1325 : "persona-invalid-response",
1326 0 : GNUNET_JSON_PACK (
1327 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1328 : response_code),
1329 : TALER_JSON_pack_ec (
1330 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1331 : GNUNET_JSON_pack_string ("detail",
1332 : "data-relationships-account-data-id"),
1333 : GNUNET_JSON_pack_allow_null (
1334 : GNUNET_JSON_pack_object_incref ("data",
1335 : (json_t *)
1336 : data))));
1337 0 : break;
1338 : }
1339 0 : persona_proof_cancel (ph);
1340 0 : }
1341 :
1342 :
1343 : /**
1344 : * Check KYC status and return final result to human.
1345 : *
1346 : * @param cls the @e cls of this struct with the plugin-specific state
1347 : * @param pd provider configuration details
1348 : * @param url_path rest of the URL after `/kyc-webhook/`
1349 : * @param connection MHD connection object (for HTTP headers)
1350 : * @param account_id which account to trigger process for
1351 : * @param process_row row in the legitimization processes table the legitimization is for
1352 : * @param provider_user_id user ID (or NULL) the proof is for
1353 : * @param inquiry_id legitimization ID the proof is for
1354 : * @param cb function to call with the result
1355 : * @param cb_cls closure for @a cb
1356 : * @return handle to cancel operation early
1357 : */
1358 : static struct TALER_KYCLOGIC_ProofHandle *
1359 0 : persona_proof (void *cls,
1360 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1361 : const char *const url_path[],
1362 : struct MHD_Connection *connection,
1363 : const struct TALER_PaytoHashP *account_id,
1364 : uint64_t process_row,
1365 : const char *provider_user_id,
1366 : const char *inquiry_id,
1367 : TALER_KYCLOGIC_ProofCallback cb,
1368 : void *cb_cls)
1369 : {
1370 0 : struct PluginState *ps = cls;
1371 : struct TALER_KYCLOGIC_ProofHandle *ph;
1372 : CURL *eh;
1373 :
1374 0 : eh = curl_easy_init ();
1375 0 : if (NULL == eh)
1376 : {
1377 0 : GNUNET_break (0);
1378 0 : return NULL;
1379 : }
1380 0 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
1381 0 : ph->ps = ps;
1382 0 : ph->pd = pd;
1383 0 : ph->cb = cb;
1384 0 : ph->cb_cls = cb_cls;
1385 0 : ph->connection = connection;
1386 0 : ph->process_row = process_row;
1387 0 : ph->h_payto = *account_id;
1388 : /* Note: we do not expect this to be non-NULL */
1389 0 : if (NULL != provider_user_id)
1390 0 : ph->provider_user_id = GNUNET_strdup (provider_user_id);
1391 0 : if (NULL != inquiry_id)
1392 0 : ph->inquiry_id = GNUNET_strdup (inquiry_id);
1393 0 : GNUNET_asprintf (&ph->url,
1394 : "https://withpersona.com/api/v1/inquiries/%s",
1395 : inquiry_id);
1396 0 : GNUNET_break (CURLE_OK ==
1397 : curl_easy_setopt (eh,
1398 : CURLOPT_VERBOSE,
1399 : 0));
1400 0 : GNUNET_assert (CURLE_OK ==
1401 : curl_easy_setopt (eh,
1402 : CURLOPT_MAXREDIRS,
1403 : 1L));
1404 0 : GNUNET_break (CURLE_OK ==
1405 : curl_easy_setopt (eh,
1406 : CURLOPT_URL,
1407 : ph->url));
1408 0 : ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
1409 : eh,
1410 0 : pd->slist,
1411 : &handle_proof_finished,
1412 : ph);
1413 0 : return ph;
1414 : }
1415 :
1416 :
1417 : /**
1418 : * Cancel KYC webhook execution.
1419 : *
1420 : * @param[in] wh handle of operation to cancel
1421 : */
1422 : static void
1423 0 : persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
1424 : {
1425 0 : if (NULL != wh->task)
1426 : {
1427 0 : GNUNET_SCHEDULER_cancel (wh->task);
1428 0 : wh->task = NULL;
1429 : }
1430 0 : if (NULL != wh->job)
1431 : {
1432 0 : GNUNET_CURL_job_cancel (wh->job);
1433 0 : wh->job = NULL;
1434 : }
1435 0 : GNUNET_free (wh->inquiry_id);
1436 0 : GNUNET_free (wh->url);
1437 0 : GNUNET_free (wh);
1438 0 : }
1439 :
1440 :
1441 : /**
1442 : * Call @a wh callback with the operation result.
1443 : *
1444 : * @param wh proof handle to generate reply for
1445 : * @param status status to return
1446 : * @param account_id account to return
1447 : * @param inquiry_id inquiry ID to supply
1448 : * @param http_status HTTP status to use
1449 : */
1450 : static void
1451 0 : webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
1452 : enum TALER_KYCLOGIC_KycStatus status,
1453 : const char *account_id,
1454 : const char *inquiry_id,
1455 : unsigned int http_status)
1456 : {
1457 : struct MHD_Response *resp;
1458 : struct GNUNET_TIME_Absolute expiration;
1459 :
1460 0 : if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
1461 0 : expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
1462 : else
1463 0 : expiration = GNUNET_TIME_UNIT_ZERO_ABS;
1464 0 : resp = MHD_create_response_from_buffer (0,
1465 : "",
1466 : MHD_RESPMEM_PERSISTENT);
1467 0 : TALER_MHD_add_global_headers (resp);
1468 0 : wh->cb (wh->cb_cls,
1469 : wh->process_row,
1470 0 : &wh->h_payto,
1471 : account_id,
1472 0 : wh->pd->section,
1473 : inquiry_id,
1474 : status,
1475 : expiration,
1476 : http_status,
1477 : resp);
1478 0 : }
1479 :
1480 :
1481 : /**
1482 : * Call @a wh callback with HTTP error response.
1483 : *
1484 : * @param wh proof handle to generate reply for
1485 : * @param inquiry_id inquiry ID to supply
1486 : * @param http_status HTTP status to use
1487 : */
1488 : static void
1489 0 : webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
1490 : const char *inquiry_id,
1491 : unsigned int http_status)
1492 : {
1493 0 : webhook_generic_reply (wh,
1494 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1495 : NULL, /* user id */
1496 : inquiry_id,
1497 : http_status);
1498 0 : }
1499 :
1500 :
1501 : /**
1502 : * Function called when we're done processing the
1503 : * HTTP "/verifications/{verification_id}" request.
1504 : *
1505 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
1506 : * @param response_code HTTP response code, 0 on error
1507 : * @param response parsed JSON result, NULL on error
1508 : */
1509 : static void
1510 0 : handle_webhook_finished (void *cls,
1511 : long response_code,
1512 : const void *response)
1513 : {
1514 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1515 0 : const json_t *j = response;
1516 0 : const json_t *data = json_object_get (j,
1517 : "data");
1518 :
1519 0 : wh->job = NULL;
1520 0 : switch (response_code)
1521 : {
1522 0 : case MHD_HTTP_OK:
1523 : {
1524 : const char *inquiry_id;
1525 : const char *account_id;
1526 0 : const char *type = NULL;
1527 : json_t *attributes;
1528 : struct GNUNET_JSON_Specification spec[] = {
1529 0 : GNUNET_JSON_spec_string ("type",
1530 : &type),
1531 0 : GNUNET_JSON_spec_string ("id",
1532 : &inquiry_id),
1533 0 : GNUNET_JSON_spec_json ("attributes",
1534 : &attributes),
1535 0 : GNUNET_JSON_spec_end ()
1536 : };
1537 :
1538 0 : if ( (NULL == data) ||
1539 : (GNUNET_OK !=
1540 0 : GNUNET_JSON_parse (data,
1541 : spec,
1542 0 : NULL, NULL)) ||
1543 0 : (0 != strcmp (type,
1544 : "inquiry")) )
1545 : {
1546 0 : GNUNET_break_op (0);
1547 0 : json_dumpf (j,
1548 : stderr,
1549 : JSON_INDENT (2));
1550 0 : webhook_reply_error (wh,
1551 : inquiry_id,
1552 : MHD_HTTP_BAD_GATEWAY);
1553 0 : break;
1554 : }
1555 :
1556 : {
1557 : const char *status; /* "completed", what else? */
1558 : const char *reference_id; /* or legitimization number */
1559 0 : const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
1560 : struct GNUNET_JSON_Specification ispec[] = {
1561 0 : GNUNET_JSON_spec_string ("status",
1562 : &status),
1563 0 : GNUNET_JSON_spec_string ("reference_id",
1564 : &reference_id),
1565 0 : GNUNET_JSON_spec_mark_optional (
1566 : GNUNET_JSON_spec_string ("expired_at",
1567 : &expired_at),
1568 : NULL),
1569 0 : GNUNET_JSON_spec_end ()
1570 : };
1571 :
1572 0 : if (GNUNET_OK !=
1573 0 : GNUNET_JSON_parse (attributes,
1574 : ispec,
1575 : NULL, NULL))
1576 : {
1577 0 : GNUNET_break_op (0);
1578 0 : json_dumpf (j,
1579 : stderr,
1580 : JSON_INDENT (2));
1581 0 : webhook_reply_error (wh,
1582 : inquiry_id,
1583 : MHD_HTTP_BAD_GATEWAY);
1584 0 : GNUNET_JSON_parse_free (ispec);
1585 0 : GNUNET_JSON_parse_free (spec);
1586 0 : break;
1587 : }
1588 : {
1589 : unsigned long long idr;
1590 : char dummy;
1591 :
1592 0 : if ( (1 != sscanf (reference_id,
1593 : "%llu%c",
1594 : &idr,
1595 0 : &dummy)) ||
1596 0 : (idr != wh->process_row) )
1597 : {
1598 0 : GNUNET_break_op (0);
1599 0 : webhook_reply_error (wh,
1600 : inquiry_id,
1601 : MHD_HTTP_BAD_GATEWAY);
1602 0 : GNUNET_JSON_parse_free (ispec);
1603 0 : GNUNET_JSON_parse_free (spec);
1604 0 : break;
1605 : }
1606 : }
1607 :
1608 0 : if (0 != strcmp (inquiry_id,
1609 0 : wh->inquiry_id))
1610 : {
1611 0 : GNUNET_break_op (0);
1612 0 : webhook_reply_error (wh,
1613 : inquiry_id,
1614 : MHD_HTTP_BAD_GATEWAY);
1615 0 : GNUNET_JSON_parse_free (ispec);
1616 0 : GNUNET_JSON_parse_free (spec);
1617 0 : break;
1618 : }
1619 :
1620 0 : account_id = json_string_value (
1621 0 : json_object_get (
1622 0 : json_object_get (
1623 0 : json_object_get (
1624 0 : json_object_get (
1625 : data,
1626 : "relationships"),
1627 : "account"),
1628 : "data"),
1629 : "id"));
1630 :
1631 0 : if (0 != strcmp (status,
1632 : "completed"))
1633 : {
1634 0 : webhook_generic_reply (wh,
1635 : TALER_KYCLOGIC_STATUS_FAILED,
1636 : account_id,
1637 : inquiry_id,
1638 : MHD_HTTP_OK);
1639 0 : GNUNET_JSON_parse_free (ispec);
1640 0 : GNUNET_JSON_parse_free (spec);
1641 0 : break;
1642 : }
1643 :
1644 0 : if (NULL == account_id)
1645 : {
1646 0 : GNUNET_break_op (0);
1647 0 : json_dumpf (data,
1648 : stderr,
1649 : JSON_INDENT (2));
1650 0 : webhook_reply_error (wh,
1651 : inquiry_id,
1652 : MHD_HTTP_BAD_GATEWAY);
1653 0 : break;
1654 : }
1655 :
1656 0 : webhook_generic_reply (wh,
1657 : TALER_KYCLOGIC_STATUS_SUCCESS,
1658 : account_id,
1659 : inquiry_id,
1660 : MHD_HTTP_OK);
1661 0 : GNUNET_JSON_parse_free (ispec);
1662 : }
1663 0 : GNUNET_JSON_parse_free (spec);
1664 0 : break;
1665 : }
1666 0 : case MHD_HTTP_BAD_REQUEST:
1667 : case MHD_HTTP_NOT_FOUND:
1668 : case MHD_HTTP_CONFLICT:
1669 : case MHD_HTTP_UNPROCESSABLE_ENTITY:
1670 : /* These are errors with this code */
1671 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1672 : "PERSONA failed with response %u:\n",
1673 : (unsigned int) response_code);
1674 0 : json_dumpf (j,
1675 : stderr,
1676 : JSON_INDENT (2));
1677 0 : webhook_reply_error (wh,
1678 0 : wh->inquiry_id,
1679 : MHD_HTTP_BAD_GATEWAY);
1680 0 : break;
1681 0 : case MHD_HTTP_UNAUTHORIZED:
1682 : /* These are failures of the exchange operator */
1683 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1684 : "Refused access with HTTP status code %u\n",
1685 : (unsigned int) response_code);
1686 0 : webhook_reply_error (wh,
1687 0 : wh->inquiry_id,
1688 : MHD_HTTP_INTERNAL_SERVER_ERROR);
1689 0 : break;
1690 0 : case MHD_HTTP_PAYMENT_REQUIRED:
1691 : /* These are failures of the exchange operator */
1692 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1693 : "Refused access with HTTP status code %u\n",
1694 : (unsigned int) response_code);
1695 :
1696 0 : webhook_reply_error (wh,
1697 0 : wh->inquiry_id,
1698 : MHD_HTTP_INTERNAL_SERVER_ERROR);
1699 0 : break;
1700 0 : case MHD_HTTP_REQUEST_TIMEOUT:
1701 : /* These are networking issues */
1702 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1703 : "PERSONA failed with response %u:\n",
1704 : (unsigned int) response_code);
1705 0 : json_dumpf (j,
1706 : stderr,
1707 : JSON_INDENT (2));
1708 0 : webhook_reply_error (wh,
1709 0 : wh->inquiry_id,
1710 : MHD_HTTP_GATEWAY_TIMEOUT);
1711 0 : break;
1712 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
1713 : /* This is a load issue */
1714 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1715 : "PERSONA failed with response %u:\n",
1716 : (unsigned int) response_code);
1717 0 : json_dumpf (j,
1718 : stderr,
1719 : JSON_INDENT (2));
1720 0 : webhook_reply_error (wh,
1721 0 : wh->inquiry_id,
1722 : MHD_HTTP_SERVICE_UNAVAILABLE);
1723 0 : break;
1724 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1725 : /* This is an issue with Persona */
1726 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1727 : "PERSONA failed with response %u:\n",
1728 : (unsigned int) response_code);
1729 0 : json_dumpf (j,
1730 : stderr,
1731 : JSON_INDENT (2));
1732 0 : webhook_reply_error (wh,
1733 0 : wh->inquiry_id,
1734 : MHD_HTTP_BAD_GATEWAY);
1735 0 : break;
1736 0 : default:
1737 : /* This is an issue with Persona */
1738 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1739 : "PERSONA failed with response %u:\n",
1740 : (unsigned int) response_code);
1741 0 : json_dumpf (j,
1742 : stderr,
1743 : JSON_INDENT (2));
1744 0 : webhook_reply_error (wh,
1745 0 : wh->inquiry_id,
1746 : MHD_HTTP_BAD_GATEWAY);
1747 0 : break;
1748 : }
1749 :
1750 0 : persona_webhook_cancel (wh);
1751 0 : }
1752 :
1753 :
1754 : /**
1755 : * Asynchronously return a reply for the webhook.
1756 : *
1757 : * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
1758 : */
1759 : static void
1760 0 : async_webhook_reply (void *cls)
1761 : {
1762 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1763 :
1764 0 : wh->task = NULL;
1765 0 : wh->cb (wh->cb_cls,
1766 : wh->process_row,
1767 0 : (0 == wh->process_row)
1768 : ? NULL
1769 : : &wh->h_payto,
1770 0 : wh->pd->section,
1771 : NULL,
1772 0 : wh->inquiry_id, /* provider legi ID */
1773 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1774 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1775 : wh->response_code,
1776 : wh->resp);
1777 0 : persona_webhook_cancel (wh);
1778 0 : }
1779 :
1780 :
1781 : /**
1782 : * Function called with the provider details and
1783 : * associated plugin closures for matching logics.
1784 : *
1785 : * @param cls closure
1786 : * @param pd provider details of a matching logic
1787 : * @param plugin_cls closure of the plugin
1788 : * @return #GNUNET_OK to continue to iterate
1789 : */
1790 : static enum GNUNET_GenericReturnValue
1791 0 : locate_details_cb (
1792 : void *cls,
1793 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1794 : void *plugin_cls)
1795 : {
1796 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1797 :
1798 : /* This type-checks 'pd' */
1799 0 : GNUNET_assert (plugin_cls == wh->ps);
1800 0 : if (0 == strcmp (pd->template_id,
1801 : wh->template_id))
1802 : {
1803 0 : wh->pd = pd;
1804 0 : return GNUNET_NO;
1805 : }
1806 0 : return GNUNET_OK;
1807 : }
1808 :
1809 :
1810 : /**
1811 : * Check KYC status and return result for Webhook. We do NOT implement the
1812 : * authentication check proposed by the PERSONA documentation, as it would
1813 : * allow an attacker who learns the access token to easily bypass the KYC
1814 : * checks. Instead, we insist on explicitly requesting the KYC status from the
1815 : * provider (at least on success).
1816 : *
1817 : * @param cls the @e cls of this struct with the plugin-specific state
1818 : * @param pd provider configuration details
1819 : * @param plc callback to lookup accounts with
1820 : * @param plc_cls closure for @a plc
1821 : * @param http_method HTTP method used for the webhook
1822 : * @param url_path rest of the URL after `/kyc-webhook/`
1823 : * @param connection MHD connection object (for HTTP headers)
1824 : * @param body HTTP request body
1825 : * @param cb function to call with the result
1826 : * @param cb_cls closure for @a cb
1827 : * @return handle to cancel operation early
1828 : */
1829 : static struct TALER_KYCLOGIC_WebhookHandle *
1830 0 : persona_webhook (void *cls,
1831 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1832 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1833 : void *plc_cls,
1834 : const char *http_method,
1835 : const char *const url_path[],
1836 : struct MHD_Connection *connection,
1837 : const json_t *body,
1838 : TALER_KYCLOGIC_WebhookCallback cb,
1839 : void *cb_cls)
1840 : {
1841 0 : struct PluginState *ps = cls;
1842 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1843 : CURL *eh;
1844 : enum GNUNET_DB_QueryStatus qs;
1845 : const char *persona_inquiry_id;
1846 : const char *auth_header;
1847 :
1848 : /* Persona webhooks are expected by logic, not by template */
1849 0 : GNUNET_break_op (NULL == pd);
1850 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1851 0 : wh->cb = cb;
1852 0 : wh->cb_cls = cb_cls;
1853 0 : wh->ps = ps;
1854 0 : wh->connection = connection;
1855 0 : wh->pd = pd;
1856 :
1857 0 : auth_header = MHD_lookup_connection_value (connection,
1858 : MHD_HEADER_KIND,
1859 : MHD_HTTP_HEADER_AUTHORIZATION);
1860 0 : if ( (NULL != ps->webhook_token) &&
1861 0 : (0 != strcmp (ps->webhook_token,
1862 : auth_header)) )
1863 : {
1864 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1865 : "Invalid authorization header `%s' received for Persona webhook\n",
1866 : auth_header);
1867 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1868 : TALER_JSON_pack_ec (
1869 : TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
1870 : GNUNET_JSON_pack_string ("detail",
1871 : "unexpected 'Authorization' header"));
1872 0 : wh->response_code = MHD_HTTP_UNAUTHORIZED;
1873 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1874 : wh);
1875 0 : return wh;
1876 : }
1877 :
1878 : wh->template_id
1879 0 : = json_string_value (
1880 0 : json_object_get (
1881 0 : json_object_get (
1882 0 : json_object_get (
1883 0 : json_object_get (
1884 0 : json_object_get (
1885 0 : json_object_get (
1886 0 : json_object_get (
1887 0 : json_object_get (
1888 : body,
1889 : "data"),
1890 : "attributes"),
1891 : "payload"),
1892 : "data"),
1893 : "relationships"),
1894 : "inquiry_template"),
1895 : "data"),
1896 : "id"));
1897 0 : if (NULL == wh->template_id)
1898 : {
1899 0 : GNUNET_break_op (0);
1900 0 : json_dumpf (body,
1901 : stderr,
1902 : JSON_INDENT (2));
1903 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1904 : TALER_JSON_pack_ec (
1905 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1906 : GNUNET_JSON_pack_string ("detail",
1907 : "data-attributes-payload-data-id"),
1908 : GNUNET_JSON_pack_object_incref ("webhook_body",
1909 : (json_t *) body));
1910 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
1911 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1912 : wh);
1913 0 : return wh;
1914 : }
1915 0 : TALER_KYCLOGIC_kyc_get_details ("persona",
1916 : &locate_details_cb,
1917 : wh);
1918 0 : if (NULL == wh->pd)
1919 : {
1920 0 : GNUNET_break_op (0);
1921 0 : json_dumpf (body,
1922 : stderr,
1923 : JSON_INDENT (2));
1924 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1925 : TALER_JSON_pack_ec (
1926 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
1927 : GNUNET_JSON_pack_string ("detail",
1928 : wh->template_id),
1929 : GNUNET_JSON_pack_object_incref ("webhook_body",
1930 : (json_t *) body));
1931 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
1932 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1933 : wh);
1934 0 : return wh;
1935 : }
1936 :
1937 :
1938 : persona_inquiry_id
1939 0 : = json_string_value (
1940 0 : json_object_get (
1941 0 : json_object_get (
1942 0 : json_object_get (
1943 0 : json_object_get (
1944 0 : json_object_get (
1945 : body,
1946 : "data"),
1947 : "attributes"),
1948 : "payload"),
1949 : "data"),
1950 : "id"));
1951 0 : if (NULL == persona_inquiry_id)
1952 : {
1953 0 : GNUNET_break_op (0);
1954 0 : json_dumpf (body,
1955 : stderr,
1956 : JSON_INDENT (2));
1957 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1958 : TALER_JSON_pack_ec (
1959 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1960 : GNUNET_JSON_pack_string ("detail",
1961 : "data-attributes-payload-data-id"),
1962 : GNUNET_JSON_pack_object_incref ("webhook_body",
1963 : (json_t *) body));
1964 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
1965 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1966 : wh);
1967 0 : return wh;
1968 : }
1969 0 : qs = plc (plc_cls,
1970 0 : wh->pd->section,
1971 : persona_inquiry_id,
1972 : &wh->h_payto,
1973 : &wh->process_row);
1974 0 : if (qs < 0)
1975 : {
1976 0 : wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
1977 : "provider-legitimization-lookup");
1978 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1979 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1980 : wh);
1981 0 : return wh;
1982 : }
1983 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1984 : {
1985 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1986 : "Received Persona kyc-webhook for unknown verification ID `%s'\n",
1987 : persona_inquiry_id);
1988 0 : wh->resp = TALER_MHD_make_error (
1989 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
1990 : persona_inquiry_id);
1991 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
1992 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1993 : wh);
1994 0 : return wh;
1995 : }
1996 0 : wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
1997 :
1998 0 : eh = curl_easy_init ();
1999 0 : if (NULL == eh)
2000 : {
2001 0 : GNUNET_break (0);
2002 0 : wh->resp = TALER_MHD_make_error (
2003 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
2004 : NULL);
2005 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2006 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2007 : wh);
2008 0 : return wh;
2009 : }
2010 :
2011 0 : GNUNET_asprintf (&wh->url,
2012 : "https://withpersona.com/api/v1/inquiries/%s",
2013 : persona_inquiry_id);
2014 0 : GNUNET_break (CURLE_OK ==
2015 : curl_easy_setopt (eh,
2016 : CURLOPT_VERBOSE,
2017 : 0));
2018 0 : GNUNET_assert (CURLE_OK ==
2019 : curl_easy_setopt (eh,
2020 : CURLOPT_MAXREDIRS,
2021 : 1L));
2022 0 : GNUNET_break (CURLE_OK ==
2023 : curl_easy_setopt (eh,
2024 : CURLOPT_URL,
2025 : wh->url));
2026 0 : wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
2027 : eh,
2028 0 : wh->pd->slist,
2029 : &handle_webhook_finished,
2030 : wh);
2031 0 : return wh;
2032 : }
2033 :
2034 :
2035 : /**
2036 : * Initialize persona logic plugin
2037 : *
2038 : * @param cls a configuration instance
2039 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
2040 : */
2041 : void *
2042 1 : libtaler_plugin_kyclogic_persona_init (void *cls)
2043 : {
2044 1 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
2045 : struct TALER_KYCLOGIC_Plugin *plugin;
2046 : struct PluginState *ps;
2047 :
2048 1 : ps = GNUNET_new (struct PluginState);
2049 1 : ps->cfg = cfg;
2050 1 : if (GNUNET_OK !=
2051 1 : GNUNET_CONFIGURATION_get_value_string (cfg,
2052 : "exchange",
2053 : "BASE_URL",
2054 : &ps->exchange_base_url))
2055 : {
2056 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
2057 : "exchange",
2058 : "BASE_URL");
2059 0 : GNUNET_free (ps);
2060 0 : return NULL;
2061 : }
2062 1 : if (GNUNET_OK !=
2063 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
2064 : "kyclogic-persona",
2065 : "WEBHOOK_AUTH_TOKEN",
2066 : &ps->webhook_token))
2067 : {
2068 : /* optional */
2069 1 : ps->webhook_token = NULL;
2070 : }
2071 :
2072 : ps->curl_ctx
2073 2 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
2074 1 : &ps->curl_rc);
2075 1 : if (NULL == ps->curl_ctx)
2076 : {
2077 0 : GNUNET_break (0);
2078 0 : GNUNET_free (ps->exchange_base_url);
2079 0 : GNUNET_free (ps);
2080 0 : return NULL;
2081 : }
2082 1 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
2083 :
2084 1 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
2085 1 : plugin->cls = ps;
2086 : plugin->load_configuration
2087 1 : = &persona_load_configuration;
2088 : plugin->unload_configuration
2089 1 : = &persona_unload_configuration;
2090 : plugin->initiate
2091 1 : = &persona_initiate;
2092 : plugin->initiate_cancel
2093 1 : = &persona_initiate_cancel;
2094 : plugin->proof
2095 1 : = &persona_proof;
2096 : plugin->proof_cancel
2097 1 : = &persona_proof_cancel;
2098 : plugin->webhook
2099 1 : = &persona_webhook;
2100 : plugin->webhook_cancel
2101 1 : = &persona_webhook_cancel;
2102 1 : return plugin;
2103 : }
2104 :
2105 :
2106 : /**
2107 : * Unload authorization plugin
2108 : *
2109 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
2110 : * @return NULL (always)
2111 : */
2112 : void *
2113 0 : libtaler_plugin_kyclogic_persona_done (void *cls)
2114 : {
2115 0 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
2116 0 : struct PluginState *ps = plugin->cls;
2117 :
2118 0 : if (NULL != ps->curl_ctx)
2119 : {
2120 0 : GNUNET_CURL_fini (ps->curl_ctx);
2121 0 : ps->curl_ctx = NULL;
2122 : }
2123 0 : if (NULL != ps->curl_rc)
2124 : {
2125 0 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
2126 0 : ps->curl_rc = NULL;
2127 : }
2128 0 : GNUNET_free (ps->exchange_base_url);
2129 0 : GNUNET_free (ps->webhook_token);
2130 0 : GNUNET_free (ps);
2131 0 : GNUNET_free (plugin);
2132 0 : return NULL;
2133 : }
2134 :
2135 :
2136 : /* end of plugin_kyclogic_persona.c */
|