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