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