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 "platform.h"
22 : #include "taler_attributes.h"
23 : #include "taler_kyclogic_plugin.h"
24 : #include "taler_mhd_lib.h"
25 : #include "taler_curl_lib.h"
26 : #include "taler_json_lib.h"
27 : #include "taler_kyclogic_lib.h"
28 : #include "taler_templating_lib.h"
29 : #include <regex.h>
30 : #include "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 0 : ph->cb (ph->cb_cls,
1096 : TALER_KYCLOGIC_STATUS_SUCCESS,
1097 0 : ph->pd->section,
1098 0 : ph->account_id,
1099 0 : ph->inquiry_id,
1100 : expiration,
1101 : attr,
1102 : MHD_HTTP_SEE_OTHER,
1103 : resp);
1104 0 : persona_proof_cancel (ph);
1105 : }
1106 :
1107 :
1108 : /**
1109 : * Function called when we're done processing the
1110 : * HTTP "/api/v1/inquiries/{inquiry-id}" request.
1111 : *
1112 : * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
1113 : * @param response_code HTTP response code, 0 on error
1114 : * @param response parsed JSON result, NULL on error
1115 : */
1116 : static void
1117 0 : handle_proof_finished (void *cls,
1118 : long response_code,
1119 : const void *response)
1120 : {
1121 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1122 0 : const json_t *j = response;
1123 0 : const json_t *data = json_object_get (j,
1124 : "data");
1125 :
1126 0 : ph->job = NULL;
1127 0 : switch (response_code)
1128 : {
1129 0 : case MHD_HTTP_OK:
1130 : {
1131 : const char *inquiry_id;
1132 : const char *account_id;
1133 0 : const char *type = NULL;
1134 : const json_t *attributes;
1135 : const json_t *relationships;
1136 : struct GNUNET_JSON_Specification spec[] = {
1137 0 : GNUNET_JSON_spec_string ("type",
1138 : &type),
1139 0 : GNUNET_JSON_spec_string ("id",
1140 : &inquiry_id),
1141 0 : GNUNET_JSON_spec_object_const ("attributes",
1142 : &attributes),
1143 0 : GNUNET_JSON_spec_object_const ("relationships",
1144 : &relationships),
1145 0 : GNUNET_JSON_spec_end ()
1146 : };
1147 :
1148 0 : if ( (NULL == data) ||
1149 : (GNUNET_OK !=
1150 0 : GNUNET_JSON_parse (data,
1151 : spec,
1152 0 : NULL, NULL)) ||
1153 0 : (0 != strcasecmp (type,
1154 : "inquiry")) )
1155 : {
1156 0 : GNUNET_break_op (0);
1157 0 : return_invalid_response (ph,
1158 : response_code,
1159 : inquiry_id,
1160 : "data",
1161 : data);
1162 0 : break;
1163 : }
1164 :
1165 : {
1166 : const char *status; /* "completed", what else? */
1167 : const char *reference_id; /* or legitimization number */
1168 0 : const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
1169 : struct GNUNET_JSON_Specification ispec[] = {
1170 0 : GNUNET_JSON_spec_string ("status",
1171 : &status),
1172 0 : GNUNET_JSON_spec_string ("reference-id",
1173 : &reference_id),
1174 0 : GNUNET_JSON_spec_mark_optional (
1175 : GNUNET_JSON_spec_string ("expired-at",
1176 : &expired_at),
1177 : NULL),
1178 0 : GNUNET_JSON_spec_end ()
1179 : };
1180 :
1181 0 : if (GNUNET_OK !=
1182 0 : GNUNET_JSON_parse (attributes,
1183 : ispec,
1184 : NULL, NULL))
1185 : {
1186 0 : GNUNET_break_op (0);
1187 0 : return_invalid_response (ph,
1188 : response_code,
1189 : inquiry_id,
1190 : "data-attributes",
1191 : data);
1192 0 : break;
1193 : }
1194 : {
1195 : unsigned long long idr;
1196 : char dummy;
1197 :
1198 0 : if ( (1 != sscanf (reference_id,
1199 : "%llu%c",
1200 : &idr,
1201 0 : &dummy)) ||
1202 0 : (idr != ph->process_row) )
1203 : {
1204 0 : GNUNET_break_op (0);
1205 0 : return_invalid_response (ph,
1206 : response_code,
1207 : inquiry_id,
1208 : "data-attributes-reference_id",
1209 : data);
1210 0 : break;
1211 : }
1212 : }
1213 :
1214 0 : if (0 != strcmp (inquiry_id,
1215 0 : ph->inquiry_id))
1216 : {
1217 0 : GNUNET_break_op (0);
1218 0 : return_invalid_response (ph,
1219 : response_code,
1220 : inquiry_id,
1221 : "data-id",
1222 : data);
1223 0 : break;
1224 : }
1225 :
1226 0 : account_id = json_string_value (
1227 0 : json_object_get (
1228 0 : json_object_get (
1229 0 : json_object_get (
1230 : relationships,
1231 : "account"),
1232 : "data"),
1233 : "id"));
1234 :
1235 0 : if (0 != strcasecmp (status,
1236 : "completed"))
1237 : {
1238 0 : proof_generic_reply (
1239 : ph,
1240 : TALER_KYCLOGIC_STATUS_FAILED,
1241 : account_id,
1242 : inquiry_id,
1243 : MHD_HTTP_OK,
1244 : "persona-kyc-failed",
1245 0 : GNUNET_JSON_PACK (
1246 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1247 : response_code),
1248 : GNUNET_JSON_pack_string ("persona_inquiry_id",
1249 : inquiry_id),
1250 : GNUNET_JSON_pack_allow_null (
1251 : GNUNET_JSON_pack_object_incref ("data",
1252 : (json_t *)
1253 : data))));
1254 0 : break;
1255 : }
1256 :
1257 0 : if (NULL == account_id)
1258 : {
1259 0 : GNUNET_break_op (0);
1260 0 : return_invalid_response (ph,
1261 : response_code,
1262 : inquiry_id,
1263 : "data-relationships-account-data-id",
1264 : data);
1265 0 : break;
1266 : }
1267 0 : ph->account_id = GNUNET_strdup (account_id);
1268 0 : ph->ec = start_conversion (ph->pd,
1269 : j,
1270 : &proof_post_conversion_cb,
1271 : ph);
1272 0 : if (NULL == ph->ec)
1273 : {
1274 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1275 : "Failed to start Persona conversion helper\n");
1276 0 : proof_reply_error (
1277 : ph,
1278 0 : ph->inquiry_id,
1279 : MHD_HTTP_BAD_GATEWAY,
1280 : "persona-logic-failure",
1281 0 : GNUNET_JSON_PACK (
1282 : TALER_JSON_pack_ec (
1283 : TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)));
1284 0 : break;
1285 : }
1286 : }
1287 0 : return; /* continued in proof_post_conversion_cb */
1288 : }
1289 0 : case MHD_HTTP_BAD_REQUEST:
1290 : case MHD_HTTP_NOT_FOUND:
1291 : case MHD_HTTP_CONFLICT:
1292 : case MHD_HTTP_UNPROCESSABLE_ENTITY:
1293 : /* These are errors with this code */
1294 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1295 : "PERSONA failed with response %u:\n",
1296 : (unsigned int) response_code);
1297 0 : json_dumpf (j,
1298 : stderr,
1299 : JSON_INDENT (2));
1300 0 : proof_reply_error (
1301 : ph,
1302 0 : ph->inquiry_id,
1303 : MHD_HTTP_BAD_GATEWAY,
1304 : "persona-logic-failure",
1305 0 : GNUNET_JSON_PACK (
1306 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1307 : response_code),
1308 : TALER_JSON_pack_ec (
1309 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1310 :
1311 : GNUNET_JSON_pack_allow_null (
1312 : GNUNET_JSON_pack_object_incref ("data",
1313 : (json_t *)
1314 : data))));
1315 0 : break;
1316 0 : case MHD_HTTP_UNAUTHORIZED:
1317 : /* These are failures of the exchange operator */
1318 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1319 : "Refused access with HTTP status code %u\n",
1320 : (unsigned int) response_code);
1321 0 : proof_reply_error (
1322 : ph,
1323 0 : ph->inquiry_id,
1324 : MHD_HTTP_BAD_GATEWAY,
1325 : "persona-exchange-unauthorized",
1326 0 : GNUNET_JSON_PACK (
1327 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1328 : response_code),
1329 : TALER_JSON_pack_ec (
1330 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
1331 : GNUNET_JSON_pack_allow_null (
1332 : GNUNET_JSON_pack_object_incref ("data",
1333 : (json_t *)
1334 : data))));
1335 0 : break;
1336 0 : case MHD_HTTP_PAYMENT_REQUIRED:
1337 : /* These are failures of the exchange operator */
1338 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1339 : "Refused access with HTTP status code %u\n",
1340 : (unsigned int) response_code);
1341 0 : proof_reply_error (
1342 : ph,
1343 0 : ph->inquiry_id,
1344 : MHD_HTTP_SERVICE_UNAVAILABLE,
1345 : "persona-exchange-unpaid",
1346 0 : GNUNET_JSON_PACK (
1347 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1348 : response_code),
1349 : TALER_JSON_pack_ec (
1350 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
1351 : GNUNET_JSON_pack_allow_null (
1352 : GNUNET_JSON_pack_object_incref ("data",
1353 : (json_t *)
1354 : data))));
1355 0 : break;
1356 0 : case MHD_HTTP_REQUEST_TIMEOUT:
1357 : /* These are networking issues */
1358 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1359 : "PERSONA failed with response %u:\n",
1360 : (unsigned int) response_code);
1361 0 : json_dumpf (j,
1362 : stderr,
1363 : JSON_INDENT (2));
1364 0 : proof_reply_error (
1365 : ph,
1366 0 : ph->inquiry_id,
1367 : MHD_HTTP_GATEWAY_TIMEOUT,
1368 : "persona-network-timeout",
1369 0 : GNUNET_JSON_PACK (
1370 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1371 : response_code),
1372 : TALER_JSON_pack_ec (
1373 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
1374 : GNUNET_JSON_pack_allow_null (
1375 : GNUNET_JSON_pack_object_incref ("data",
1376 : (json_t *)
1377 : data))));
1378 0 : break;
1379 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
1380 : /* This is a load issue */
1381 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1382 : "PERSONA failed with response %u:\n",
1383 : (unsigned int) response_code);
1384 0 : json_dumpf (j,
1385 : stderr,
1386 : JSON_INDENT (2));
1387 0 : proof_reply_error (
1388 : ph,
1389 0 : ph->inquiry_id,
1390 : MHD_HTTP_SERVICE_UNAVAILABLE,
1391 : "persona-load-failure",
1392 0 : GNUNET_JSON_PACK (
1393 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1394 : response_code),
1395 : TALER_JSON_pack_ec (
1396 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
1397 : GNUNET_JSON_pack_allow_null (
1398 : GNUNET_JSON_pack_object_incref ("data",
1399 : (json_t *)
1400 : data))));
1401 0 : break;
1402 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1403 : /* This is an issue with Persona */
1404 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1405 : "PERSONA failed with response %u:\n",
1406 : (unsigned int) response_code);
1407 0 : json_dumpf (j,
1408 : stderr,
1409 : JSON_INDENT (2));
1410 0 : proof_reply_error (
1411 : ph,
1412 0 : ph->inquiry_id,
1413 : MHD_HTTP_BAD_GATEWAY,
1414 : "persona-provider-failure",
1415 0 : GNUNET_JSON_PACK (
1416 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1417 : response_code),
1418 : TALER_JSON_pack_ec (
1419 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
1420 : GNUNET_JSON_pack_allow_null (
1421 : GNUNET_JSON_pack_object_incref ("data",
1422 : (json_t *)
1423 : data))));
1424 0 : break;
1425 0 : default:
1426 : /* This is an issue with Persona */
1427 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1428 : "PERSONA failed with response %u:\n",
1429 : (unsigned int) response_code);
1430 0 : json_dumpf (j,
1431 : stderr,
1432 : JSON_INDENT (2));
1433 0 : proof_reply_error (
1434 : ph,
1435 0 : ph->inquiry_id,
1436 : MHD_HTTP_BAD_GATEWAY,
1437 : "persona-invalid-response",
1438 0 : GNUNET_JSON_PACK (
1439 : GNUNET_JSON_pack_uint64 ("persona_http_status",
1440 : response_code),
1441 : TALER_JSON_pack_ec (
1442 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
1443 : GNUNET_JSON_pack_allow_null (
1444 : GNUNET_JSON_pack_object_incref ("data",
1445 : (json_t *)
1446 : data))));
1447 0 : break;
1448 : }
1449 0 : persona_proof_cancel (ph);
1450 : }
1451 :
1452 :
1453 : /**
1454 : * Check KYC status and return final result to human.
1455 : *
1456 : * @param cls the @e cls of this struct with the plugin-specific state
1457 : * @param pd provider configuration details
1458 : * @param connection MHD connection object (for HTTP headers)
1459 : * @param account_id which account to trigger process for
1460 : * @param process_row row in the legitimization processes table the legitimization is for
1461 : * @param provider_user_id user ID (or NULL) the proof is for
1462 : * @param inquiry_id legitimization ID the proof is for
1463 : * @param cb function to call with the result
1464 : * @param cb_cls closure for @a cb
1465 : * @return handle to cancel operation early
1466 : */
1467 : static struct TALER_KYCLOGIC_ProofHandle *
1468 0 : persona_proof (void *cls,
1469 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1470 : struct MHD_Connection *connection,
1471 : const struct TALER_NormalizedPaytoHashP *account_id,
1472 : uint64_t process_row,
1473 : const char *provider_user_id,
1474 : const char *inquiry_id,
1475 : TALER_KYCLOGIC_ProofCallback cb,
1476 : void *cb_cls)
1477 : {
1478 0 : struct PluginState *ps = cls;
1479 : struct TALER_KYCLOGIC_ProofHandle *ph;
1480 : CURL *eh;
1481 :
1482 0 : eh = curl_easy_init ();
1483 0 : if (NULL == eh)
1484 : {
1485 0 : GNUNET_break (0);
1486 0 : return NULL;
1487 : }
1488 0 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
1489 0 : ph->ps = ps;
1490 0 : ph->pd = pd;
1491 0 : ph->cb = cb;
1492 0 : ph->cb_cls = cb_cls;
1493 0 : ph->connection = connection;
1494 0 : ph->process_row = process_row;
1495 0 : ph->h_payto = *account_id;
1496 : /* Note: we do not expect this to be non-NULL */
1497 0 : if (NULL != provider_user_id)
1498 0 : ph->provider_user_id = GNUNET_strdup (provider_user_id);
1499 0 : if (NULL != inquiry_id)
1500 0 : ph->inquiry_id = GNUNET_strdup (inquiry_id);
1501 0 : GNUNET_asprintf (&ph->url,
1502 : "https://withpersona.com/api/v1/inquiries/%s",
1503 : inquiry_id);
1504 0 : GNUNET_break (CURLE_OK ==
1505 : curl_easy_setopt (eh,
1506 : CURLOPT_VERBOSE,
1507 : 0));
1508 0 : GNUNET_assert (CURLE_OK ==
1509 : curl_easy_setopt (eh,
1510 : CURLOPT_MAXREDIRS,
1511 : 1L));
1512 0 : GNUNET_break (CURLE_OK ==
1513 : curl_easy_setopt (eh,
1514 : CURLOPT_URL,
1515 : ph->url));
1516 0 : ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
1517 : eh,
1518 0 : pd->slist,
1519 : &handle_proof_finished,
1520 : ph);
1521 0 : return ph;
1522 : }
1523 :
1524 :
1525 : /**
1526 : * Cancel KYC webhook execution.
1527 : *
1528 : * @param[in] wh handle of operation to cancel
1529 : */
1530 : static void
1531 0 : persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
1532 : {
1533 0 : if (NULL != wh->task)
1534 : {
1535 0 : GNUNET_SCHEDULER_cancel (wh->task);
1536 0 : wh->task = NULL;
1537 : }
1538 0 : if (NULL != wh->job)
1539 : {
1540 0 : GNUNET_CURL_job_cancel (wh->job);
1541 0 : wh->job = NULL;
1542 : }
1543 0 : if (NULL != wh->ec)
1544 : {
1545 0 : TALER_JSON_external_conversion_stop (wh->ec);
1546 0 : wh->ec = NULL;
1547 : }
1548 0 : GNUNET_free (wh->account_id);
1549 0 : GNUNET_free (wh->inquiry_id);
1550 0 : GNUNET_free (wh->url);
1551 0 : GNUNET_free (wh);
1552 0 : }
1553 :
1554 :
1555 : /**
1556 : * Call @a wh callback with the operation result.
1557 : *
1558 : * @param wh proof handle to generate reply for
1559 : * @param status status to return
1560 : * @param account_id account to return
1561 : * @param inquiry_id inquiry ID to supply
1562 : * @param attr KYC attribute data for the client
1563 : * @param http_status HTTP status to use
1564 : */
1565 : static void
1566 0 : webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
1567 : enum TALER_KYCLOGIC_KycStatus status,
1568 : const char *account_id,
1569 : const char *inquiry_id,
1570 : const json_t *attr,
1571 : unsigned int http_status)
1572 : {
1573 : struct MHD_Response *resp;
1574 : struct GNUNET_TIME_Absolute expiration;
1575 :
1576 0 : if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
1577 0 : expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
1578 : else
1579 0 : expiration = GNUNET_TIME_UNIT_ZERO_ABS;
1580 0 : resp = MHD_create_response_from_buffer_static (0,
1581 : "");
1582 0 : TALER_MHD_add_global_headers (resp);
1583 0 : wh->cb (wh->cb_cls,
1584 : wh->process_row,
1585 0 : &wh->h_payto,
1586 0 : wh->is_wallet,
1587 0 : wh->pd->section,
1588 : account_id,
1589 : inquiry_id,
1590 : status,
1591 : expiration,
1592 : attr,
1593 : http_status,
1594 : resp);
1595 0 : }
1596 :
1597 :
1598 : /**
1599 : * Call @a wh callback with HTTP error response.
1600 : *
1601 : * @param wh proof handle to generate reply for
1602 : * @param inquiry_id inquiry ID to supply
1603 : * @param http_status HTTP status to use
1604 : */
1605 : static void
1606 0 : webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
1607 : const char *inquiry_id,
1608 : unsigned int http_status)
1609 : {
1610 0 : webhook_generic_reply (wh,
1611 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1612 : NULL, /* user id */
1613 : inquiry_id,
1614 : NULL, /* attributes */
1615 : http_status);
1616 0 : }
1617 :
1618 :
1619 : /**
1620 : * Type of a callback that receives a JSON @a result.
1621 : *
1622 : * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
1623 : * @param status_type how did the process die
1624 : * @param code termination status code from the process
1625 : * @param attr some JSON result, NULL if we failed to get an JSON output
1626 : */
1627 : static void
1628 0 : webhook_post_conversion_cb (void *cls,
1629 : enum GNUNET_OS_ProcessStatusType status_type,
1630 : unsigned long code,
1631 : const json_t *attr)
1632 : {
1633 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1634 :
1635 0 : wh->ec = NULL;
1636 0 : webhook_generic_reply (wh,
1637 : TALER_KYCLOGIC_STATUS_SUCCESS,
1638 0 : wh->account_id,
1639 0 : wh->inquiry_id,
1640 : attr,
1641 : MHD_HTTP_OK);
1642 0 : }
1643 :
1644 :
1645 : /**
1646 : * Function called when we're done processing the
1647 : * HTTP "/api/v1/inquiries/{inquiry_id}" request.
1648 : *
1649 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
1650 : * @param response_code HTTP response code, 0 on error
1651 : * @param response parsed JSON result, NULL on error
1652 : */
1653 : static void
1654 0 : handle_webhook_finished (void *cls,
1655 : long response_code,
1656 : const void *response)
1657 : {
1658 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1659 0 : const json_t *j = response;
1660 0 : const json_t *data = json_object_get (j,
1661 : "data");
1662 :
1663 0 : wh->job = NULL;
1664 0 : switch (response_code)
1665 : {
1666 0 : case MHD_HTTP_OK:
1667 : {
1668 : const char *inquiry_id;
1669 : const char *account_id;
1670 0 : const char *type = NULL;
1671 : const json_t *attributes;
1672 : const json_t *relationships;
1673 : struct GNUNET_JSON_Specification spec[] = {
1674 0 : GNUNET_JSON_spec_string ("type",
1675 : &type),
1676 0 : GNUNET_JSON_spec_string ("id",
1677 : &inquiry_id),
1678 0 : GNUNET_JSON_spec_object_const ("attributes",
1679 : &attributes),
1680 0 : GNUNET_JSON_spec_object_const ("relationships",
1681 : &relationships),
1682 0 : GNUNET_JSON_spec_end ()
1683 : };
1684 :
1685 0 : if ( (NULL == data) ||
1686 : (GNUNET_OK !=
1687 0 : GNUNET_JSON_parse (data,
1688 : spec,
1689 0 : NULL, NULL)) ||
1690 0 : (0 != strcasecmp (type,
1691 : "inquiry")) )
1692 : {
1693 0 : GNUNET_break_op (0);
1694 0 : json_dumpf (j,
1695 : stderr,
1696 : JSON_INDENT (2));
1697 0 : webhook_reply_error (wh,
1698 : inquiry_id,
1699 : MHD_HTTP_BAD_GATEWAY);
1700 0 : break;
1701 : }
1702 :
1703 : {
1704 : const char *status; /* "completed", what else? */
1705 : const char *reference_id; /* or legitimization number */
1706 0 : const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
1707 : struct GNUNET_JSON_Specification ispec[] = {
1708 0 : GNUNET_JSON_spec_string ("status",
1709 : &status),
1710 0 : GNUNET_JSON_spec_string ("reference-id",
1711 : &reference_id),
1712 0 : GNUNET_JSON_spec_mark_optional (
1713 : GNUNET_JSON_spec_string ("expired-at",
1714 : &expired_at),
1715 : NULL),
1716 0 : GNUNET_JSON_spec_end ()
1717 : };
1718 :
1719 0 : if (GNUNET_OK !=
1720 0 : GNUNET_JSON_parse (attributes,
1721 : ispec,
1722 : NULL, NULL))
1723 : {
1724 0 : GNUNET_break_op (0);
1725 0 : json_dumpf (j,
1726 : stderr,
1727 : JSON_INDENT (2));
1728 0 : webhook_reply_error (wh,
1729 : inquiry_id,
1730 : MHD_HTTP_BAD_GATEWAY);
1731 0 : break;
1732 : }
1733 : {
1734 : unsigned long long idr;
1735 : char dummy;
1736 :
1737 0 : if ( (1 != sscanf (reference_id,
1738 : "%llu%c",
1739 : &idr,
1740 0 : &dummy)) ||
1741 0 : (idr != wh->process_row) )
1742 : {
1743 0 : GNUNET_break_op (0);
1744 0 : webhook_reply_error (wh,
1745 : inquiry_id,
1746 : MHD_HTTP_BAD_GATEWAY);
1747 0 : break;
1748 : }
1749 : }
1750 :
1751 0 : if (0 != strcmp (inquiry_id,
1752 0 : wh->inquiry_id))
1753 : {
1754 0 : GNUNET_break_op (0);
1755 0 : webhook_reply_error (wh,
1756 : inquiry_id,
1757 : MHD_HTTP_BAD_GATEWAY);
1758 0 : break;
1759 : }
1760 :
1761 0 : account_id = json_string_value (
1762 0 : json_object_get (
1763 0 : json_object_get (
1764 0 : json_object_get (
1765 : relationships,
1766 : "account"),
1767 : "data"),
1768 : "id"));
1769 :
1770 0 : if (0 != strcasecmp (status,
1771 : "completed"))
1772 : {
1773 0 : webhook_generic_reply (wh,
1774 : TALER_KYCLOGIC_STATUS_FAILED,
1775 : account_id,
1776 : inquiry_id,
1777 : NULL,
1778 : MHD_HTTP_OK);
1779 0 : break;
1780 : }
1781 :
1782 0 : if (NULL == account_id)
1783 : {
1784 0 : GNUNET_break_op (0);
1785 0 : json_dumpf (data,
1786 : stderr,
1787 : JSON_INDENT (2));
1788 0 : webhook_reply_error (wh,
1789 : inquiry_id,
1790 : MHD_HTTP_BAD_GATEWAY);
1791 0 : break;
1792 : }
1793 0 : wh->account_id = GNUNET_strdup (account_id);
1794 0 : wh->ec = start_conversion (wh->pd,
1795 : j,
1796 : &webhook_post_conversion_cb,
1797 : wh);
1798 0 : if (NULL == wh->ec)
1799 : {
1800 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1801 : "Failed to start Persona conversion helper\n");
1802 0 : webhook_reply_error (wh,
1803 : inquiry_id,
1804 : MHD_HTTP_INTERNAL_SERVER_ERROR);
1805 0 : break;
1806 : }
1807 : }
1808 0 : return; /* continued in webhook_post_conversion_cb */
1809 : }
1810 0 : case MHD_HTTP_BAD_REQUEST:
1811 : case MHD_HTTP_NOT_FOUND:
1812 : case MHD_HTTP_CONFLICT:
1813 : case MHD_HTTP_UNPROCESSABLE_ENTITY:
1814 : /* These are errors with this code */
1815 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1816 : "PERSONA failed with response %u:\n",
1817 : (unsigned int) response_code);
1818 0 : json_dumpf (j,
1819 : stderr,
1820 : JSON_INDENT (2));
1821 0 : webhook_reply_error (wh,
1822 0 : wh->inquiry_id,
1823 : MHD_HTTP_BAD_GATEWAY);
1824 0 : break;
1825 0 : case MHD_HTTP_UNAUTHORIZED:
1826 : /* These are failures of the exchange operator */
1827 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1828 : "Refused access with HTTP status code %u\n",
1829 : (unsigned int) response_code);
1830 0 : webhook_reply_error (wh,
1831 0 : wh->inquiry_id,
1832 : MHD_HTTP_INTERNAL_SERVER_ERROR);
1833 0 : break;
1834 0 : case MHD_HTTP_PAYMENT_REQUIRED:
1835 : /* These are failures of the exchange operator */
1836 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1837 : "Refused access with HTTP status code %u\n",
1838 : (unsigned int) response_code);
1839 :
1840 0 : webhook_reply_error (wh,
1841 0 : wh->inquiry_id,
1842 : MHD_HTTP_INTERNAL_SERVER_ERROR);
1843 0 : break;
1844 0 : case MHD_HTTP_REQUEST_TIMEOUT:
1845 : /* These are networking issues */
1846 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1847 : "PERSONA failed with response %u:\n",
1848 : (unsigned int) response_code);
1849 0 : json_dumpf (j,
1850 : stderr,
1851 : JSON_INDENT (2));
1852 0 : webhook_reply_error (wh,
1853 0 : wh->inquiry_id,
1854 : MHD_HTTP_GATEWAY_TIMEOUT);
1855 0 : break;
1856 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
1857 : /* This is a load issue */
1858 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1859 : "PERSONA failed with response %u:\n",
1860 : (unsigned int) response_code);
1861 0 : json_dumpf (j,
1862 : stderr,
1863 : JSON_INDENT (2));
1864 0 : webhook_reply_error (wh,
1865 0 : wh->inquiry_id,
1866 : MHD_HTTP_SERVICE_UNAVAILABLE);
1867 0 : break;
1868 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
1869 : /* This is an issue with Persona */
1870 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1871 : "PERSONA failed with response %u:\n",
1872 : (unsigned int) response_code);
1873 0 : json_dumpf (j,
1874 : stderr,
1875 : JSON_INDENT (2));
1876 0 : webhook_reply_error (wh,
1877 0 : wh->inquiry_id,
1878 : MHD_HTTP_BAD_GATEWAY);
1879 0 : break;
1880 0 : default:
1881 : /* This is an issue with Persona */
1882 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1883 : "PERSONA failed with response %u:\n",
1884 : (unsigned int) response_code);
1885 0 : json_dumpf (j,
1886 : stderr,
1887 : JSON_INDENT (2));
1888 0 : webhook_reply_error (wh,
1889 0 : wh->inquiry_id,
1890 : MHD_HTTP_BAD_GATEWAY);
1891 0 : break;
1892 : }
1893 :
1894 0 : persona_webhook_cancel (wh);
1895 : }
1896 :
1897 :
1898 : /**
1899 : * Asynchronously return a reply for the webhook.
1900 : *
1901 : * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
1902 : */
1903 : static void
1904 0 : async_webhook_reply (void *cls)
1905 : {
1906 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1907 :
1908 0 : wh->task = NULL;
1909 0 : wh->cb (wh->cb_cls,
1910 : wh->process_row,
1911 0 : (0 == wh->process_row)
1912 : ? NULL
1913 : : &wh->h_payto,
1914 0 : wh->is_wallet,
1915 0 : wh->pd->section,
1916 : NULL,
1917 0 : wh->inquiry_id, /* provider legi ID */
1918 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1919 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1920 : NULL,
1921 : wh->response_code,
1922 : wh->resp);
1923 0 : persona_webhook_cancel (wh);
1924 0 : }
1925 :
1926 :
1927 : /**
1928 : * Function called with the provider details and
1929 : * associated plugin closures for matching logics.
1930 : *
1931 : * @param cls closure
1932 : * @param pd provider details of a matching logic
1933 : * @param plugin_cls closure of the plugin
1934 : * @return #GNUNET_OK to continue to iterate
1935 : */
1936 : static enum GNUNET_GenericReturnValue
1937 0 : locate_details_cb (
1938 : void *cls,
1939 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1940 : void *plugin_cls)
1941 : {
1942 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1943 :
1944 : /* This type-checks 'pd' */
1945 0 : GNUNET_assert (plugin_cls == wh->ps);
1946 0 : if (0 == strcmp (pd->template_id,
1947 : wh->template_id))
1948 : {
1949 0 : wh->pd = pd;
1950 0 : return GNUNET_NO;
1951 : }
1952 0 : return GNUNET_OK;
1953 : }
1954 :
1955 :
1956 : /**
1957 : * Check KYC status and return result for Webhook. We do NOT implement the
1958 : * authentication check proposed by the PERSONA documentation, as it would
1959 : * allow an attacker who learns the access token to easily bypass the KYC
1960 : * checks. Instead, we insist on explicitly requesting the KYC status from the
1961 : * provider (at least on success).
1962 : *
1963 : * @param cls the @e cls of this struct with the plugin-specific state
1964 : * @param pd provider configuration details
1965 : * @param plc callback to lookup accounts with
1966 : * @param plc_cls closure for @a plc
1967 : * @param http_method HTTP method used for the webhook
1968 : * @param url_path rest of the URL after `/kyc-webhook/`
1969 : * @param connection MHD connection object (for HTTP headers)
1970 : * @param body HTTP request body
1971 : * @param cb function to call with the result
1972 : * @param cb_cls closure for @a cb
1973 : * @return handle to cancel operation early
1974 : */
1975 : static struct TALER_KYCLOGIC_WebhookHandle *
1976 0 : persona_webhook (void *cls,
1977 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1978 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1979 : void *plc_cls,
1980 : const char *http_method,
1981 : const char *const url_path[],
1982 : struct MHD_Connection *connection,
1983 : const json_t *body,
1984 : TALER_KYCLOGIC_WebhookCallback cb,
1985 : void *cb_cls)
1986 : {
1987 0 : struct PluginState *ps = cls;
1988 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1989 : CURL *eh;
1990 : enum GNUNET_DB_QueryStatus qs;
1991 : const char *persona_inquiry_id;
1992 : const char *auth_header;
1993 :
1994 : /* Persona webhooks are expected by logic, not by template */
1995 0 : GNUNET_break_op (NULL == pd);
1996 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1997 0 : wh->cb = cb;
1998 0 : wh->cb_cls = cb_cls;
1999 0 : wh->ps = ps;
2000 0 : wh->connection = connection;
2001 0 : wh->pd = pd;
2002 0 : auth_header = MHD_lookup_connection_value (connection,
2003 : MHD_HEADER_KIND,
2004 : MHD_HTTP_HEADER_AUTHORIZATION);
2005 0 : if ( (NULL != ps->webhook_token) &&
2006 0 : ( (NULL == auth_header) ||
2007 0 : (0 != strcmp (ps->webhook_token,
2008 : auth_header)) ) )
2009 : {
2010 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2011 : "Invalid authorization header `%s' received for Persona webhook\n",
2012 : auth_header);
2013 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
2014 : TALER_JSON_pack_ec (
2015 : TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
2016 : GNUNET_JSON_pack_string ("detail",
2017 : "unexpected 'Authorization' header"));
2018 0 : wh->response_code = MHD_HTTP_UNAUTHORIZED;
2019 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2020 : wh);
2021 0 : return wh;
2022 : }
2023 :
2024 : wh->template_id
2025 0 : = json_string_value (
2026 0 : json_object_get (
2027 0 : json_object_get (
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 : body,
2035 : "data"),
2036 : "attributes"),
2037 : "payload"),
2038 : "data"),
2039 : "relationships"),
2040 : "inquiry-template"),
2041 : "data"),
2042 : "id"));
2043 0 : if (NULL == wh->template_id)
2044 : {
2045 0 : GNUNET_break_op (0);
2046 0 : json_dumpf (body,
2047 : stderr,
2048 : JSON_INDENT (2));
2049 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
2050 : TALER_JSON_pack_ec (
2051 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
2052 : GNUNET_JSON_pack_string ("detail",
2053 : "data-attributes-payload-data-id"),
2054 : GNUNET_JSON_pack_object_incref ("webhook_body",
2055 : (json_t *) body));
2056 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
2057 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2058 : wh);
2059 0 : return wh;
2060 : }
2061 0 : TALER_KYCLOGIC_kyc_get_details ("persona",
2062 : &locate_details_cb,
2063 : wh);
2064 0 : if (NULL == wh->pd)
2065 : {
2066 0 : GNUNET_break_op (0);
2067 0 : json_dumpf (body,
2068 : stderr,
2069 : JSON_INDENT (2));
2070 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
2071 : TALER_JSON_pack_ec (
2072 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
2073 : GNUNET_JSON_pack_string ("detail",
2074 : wh->template_id),
2075 : GNUNET_JSON_pack_object_incref ("webhook_body",
2076 : (json_t *) body));
2077 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
2078 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2079 : wh);
2080 0 : return wh;
2081 : }
2082 :
2083 : persona_inquiry_id
2084 0 : = json_string_value (
2085 0 : json_object_get (
2086 0 : json_object_get (
2087 0 : json_object_get (
2088 0 : json_object_get (
2089 0 : json_object_get (
2090 : body,
2091 : "data"),
2092 : "attributes"),
2093 : "payload"),
2094 : "data"),
2095 : "id"));
2096 0 : if (NULL == persona_inquiry_id)
2097 : {
2098 0 : GNUNET_break_op (0);
2099 0 : json_dumpf (body,
2100 : stderr,
2101 : JSON_INDENT (2));
2102 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
2103 : TALER_JSON_pack_ec (
2104 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
2105 : GNUNET_JSON_pack_string ("detail",
2106 : "data-attributes-payload-data-id"),
2107 : GNUNET_JSON_pack_object_incref ("webhook_body",
2108 : (json_t *) body));
2109 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
2110 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2111 : wh);
2112 0 : return wh;
2113 : }
2114 0 : qs = plc (plc_cls,
2115 0 : wh->pd->section,
2116 : persona_inquiry_id,
2117 : &wh->h_payto,
2118 : &wh->is_wallet,
2119 : &wh->process_row);
2120 0 : if (qs < 0)
2121 : {
2122 0 : wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
2123 : "provider-legitimization-lookup");
2124 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2125 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2126 : wh);
2127 0 : return wh;
2128 : }
2129 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
2130 : {
2131 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2132 : "Received Persona kyc-webhook for unknown verification ID `%s'\n",
2133 : persona_inquiry_id);
2134 0 : wh->resp = TALER_MHD_make_error (
2135 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
2136 : persona_inquiry_id);
2137 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
2138 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2139 : wh);
2140 0 : return wh;
2141 : }
2142 0 : wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
2143 :
2144 0 : eh = curl_easy_init ();
2145 0 : if (NULL == eh)
2146 : {
2147 0 : GNUNET_break (0);
2148 0 : wh->resp = TALER_MHD_make_error (
2149 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
2150 : NULL);
2151 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2152 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
2153 : wh);
2154 0 : return wh;
2155 : }
2156 :
2157 0 : GNUNET_asprintf (&wh->url,
2158 : "https://withpersona.com/api/v1/inquiries/%s",
2159 : persona_inquiry_id);
2160 0 : GNUNET_break (CURLE_OK ==
2161 : curl_easy_setopt (eh,
2162 : CURLOPT_VERBOSE,
2163 : 0));
2164 0 : GNUNET_assert (CURLE_OK ==
2165 : curl_easy_setopt (eh,
2166 : CURLOPT_MAXREDIRS,
2167 : 1L));
2168 0 : GNUNET_break (CURLE_OK ==
2169 : curl_easy_setopt (eh,
2170 : CURLOPT_URL,
2171 : wh->url));
2172 0 : wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
2173 : eh,
2174 0 : wh->pd->slist,
2175 : &handle_webhook_finished,
2176 : wh);
2177 0 : return wh;
2178 : }
2179 :
2180 :
2181 : /**
2182 : * Initialize persona logic plugin
2183 : *
2184 : * @param cls a configuration instance
2185 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
2186 : */
2187 : void *
2188 : libtaler_plugin_kyclogic_persona_init (void *cls);
2189 :
2190 : /* declaration to avoid compiler warning */
2191 : void *
2192 76 : libtaler_plugin_kyclogic_persona_init (void *cls)
2193 : {
2194 76 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
2195 : struct TALER_KYCLOGIC_Plugin *plugin;
2196 : struct PluginState *ps;
2197 :
2198 76 : ps = GNUNET_new (struct PluginState);
2199 76 : ps->cfg = cfg;
2200 76 : if (GNUNET_OK !=
2201 76 : GNUNET_CONFIGURATION_get_value_string (cfg,
2202 : "exchange",
2203 : "BASE_URL",
2204 : &ps->exchange_base_url))
2205 : {
2206 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
2207 : "exchange",
2208 : "BASE_URL");
2209 0 : GNUNET_free (ps);
2210 0 : return NULL;
2211 : }
2212 76 : if (GNUNET_OK !=
2213 76 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
2214 : "kyclogic-persona",
2215 : "WEBHOOK_AUTH_TOKEN",
2216 : &ps->webhook_token))
2217 : {
2218 : /* optional */
2219 76 : ps->webhook_token = NULL;
2220 : }
2221 :
2222 : ps->curl_ctx
2223 152 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
2224 76 : &ps->curl_rc);
2225 76 : if (NULL == ps->curl_ctx)
2226 : {
2227 0 : GNUNET_break (0);
2228 0 : GNUNET_free (ps->exchange_base_url);
2229 0 : GNUNET_free (ps);
2230 0 : return NULL;
2231 : }
2232 76 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
2233 :
2234 76 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
2235 76 : plugin->cls = ps;
2236 : plugin->load_configuration
2237 76 : = &persona_load_configuration;
2238 : plugin->unload_configuration
2239 76 : = &persona_unload_configuration;
2240 : plugin->initiate
2241 76 : = &persona_initiate;
2242 : plugin->initiate_cancel
2243 76 : = &persona_initiate_cancel;
2244 : plugin->proof
2245 76 : = &persona_proof;
2246 : plugin->proof_cancel
2247 76 : = &persona_proof_cancel;
2248 : plugin->webhook
2249 76 : = &persona_webhook;
2250 : plugin->webhook_cancel
2251 76 : = &persona_webhook_cancel;
2252 76 : return plugin;
2253 : }
2254 :
2255 :
2256 : /**
2257 : * Unload authorization plugin
2258 : *
2259 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
2260 : * @return NULL (always)
2261 : */
2262 : void *
2263 : libtaler_plugin_kyclogic_persona_done (void *cls);
2264 :
2265 : /* declaration to avoid compiler warning */
2266 :
2267 : void *
2268 76 : libtaler_plugin_kyclogic_persona_done (void *cls)
2269 : {
2270 76 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
2271 76 : struct PluginState *ps = plugin->cls;
2272 :
2273 76 : if (NULL != ps->curl_ctx)
2274 : {
2275 76 : GNUNET_CURL_fini (ps->curl_ctx);
2276 76 : ps->curl_ctx = NULL;
2277 : }
2278 76 : if (NULL != ps->curl_rc)
2279 : {
2280 76 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
2281 76 : ps->curl_rc = NULL;
2282 : }
2283 76 : GNUNET_free (ps->exchange_base_url);
2284 76 : GNUNET_free (ps->webhook_token);
2285 76 : GNUNET_free (ps);
2286 76 : GNUNET_free (plugin);
2287 76 : return NULL;
2288 : }
2289 :
2290 :
2291 : /* end of plugin_kyclogic_persona.c */
|