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