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