Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : Copyright (C) 2022 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_kycaid.c
18 : * @brief kycaid for an authentication flow logic
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler_kyclogic_plugin.h"
23 : #include "taler_mhd_lib.h"
24 : #include "taler_curl_lib.h"
25 : #include "taler_json_lib.h"
26 : #include <regex.h>
27 : #include "taler_util.h"
28 :
29 :
30 : /**
31 : * Saves the state of a plugin.
32 : */
33 : struct PluginState
34 : {
35 :
36 : /**
37 : * Our base URL.
38 : */
39 : char *exchange_base_url;
40 :
41 : /**
42 : * Our global configuration.
43 : */
44 : const struct GNUNET_CONFIGURATION_Handle *cfg;
45 :
46 : /**
47 : * Context for CURL operations (useful to the event loop)
48 : */
49 : struct GNUNET_CURL_Context *curl_ctx;
50 :
51 : /**
52 : * Context for integrating @e curl_ctx with the
53 : * GNUnet event loop.
54 : */
55 : struct GNUNET_CURL_RescheduleContext *curl_rc;
56 :
57 : };
58 :
59 :
60 : /**
61 : * Keeps the plugin-specific state for
62 : * a given configuration section.
63 : */
64 : struct TALER_KYCLOGIC_ProviderDetails
65 : {
66 :
67 : /**
68 : * Overall plugin state.
69 : */
70 : struct PluginState *ps;
71 :
72 : /**
73 : * Configuration section that configured us.
74 : */
75 : char *section;
76 :
77 : /**
78 : * Authorization token to use when talking
79 : * to the service.
80 : */
81 : char *auth_token;
82 :
83 : /**
84 : * Form ID for the KYC check to perform.
85 : */
86 : char *form_id;
87 :
88 : /**
89 : * Validity time for a successful KYC process.
90 : */
91 : struct GNUNET_TIME_Relative validity;
92 :
93 : /**
94 : * Curl-ready authentication header to use.
95 : */
96 : struct curl_slist *slist;
97 :
98 : };
99 :
100 :
101 : /**
102 : * Handle for an initiation operation.
103 : */
104 : struct TALER_KYCLOGIC_InitiateHandle
105 : {
106 :
107 : /**
108 : * Hash of the payto:// URI we are initiating
109 : * the KYC for.
110 : */
111 : struct TALER_PaytoHashP h_payto;
112 :
113 : /**
114 : * UUID being checked.
115 : */
116 : uint64_t legitimization_uuid;
117 :
118 : /**
119 : * Our configuration details.
120 : */
121 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
122 :
123 : /**
124 : * Continuation to call.
125 : */
126 : TALER_KYCLOGIC_InitiateCallback cb;
127 :
128 : /**
129 : * Closure for @a cb.
130 : */
131 : void *cb_cls;
132 :
133 : /**
134 : * Context for #TEH_curl_easy_post(). Keeps the data that must
135 : * persist for Curl to make the upload.
136 : */
137 : struct TALER_CURL_PostContext ctx;
138 :
139 : /**
140 : * Handle for the request.
141 : */
142 : struct GNUNET_CURL_Job *job;
143 :
144 : /**
145 : * URL of the cURL request.
146 : */
147 : char *url;
148 :
149 : };
150 :
151 :
152 : /**
153 : * Handle for an KYC proof operation.
154 : */
155 : struct TALER_KYCLOGIC_ProofHandle
156 : {
157 :
158 : /**
159 : * Overall plugin state.
160 : */
161 : struct PluginState *ps;
162 :
163 : /**
164 : * Our configuration details.
165 : */
166 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
167 :
168 : /**
169 : * Continuation to call.
170 : */
171 : TALER_KYCLOGIC_ProofCallback cb;
172 :
173 : /**
174 : * Closure for @e cb.
175 : */
176 : void *cb_cls;
177 :
178 : /**
179 : * Connection we are handling.
180 : */
181 : struct MHD_Connection *connection;
182 :
183 : /**
184 : * Task for asynchronous execution.
185 : */
186 : struct GNUNET_SCHEDULER_Task *task;
187 : };
188 :
189 :
190 : /**
191 : * Handle for an KYC Web hook operation.
192 : */
193 : struct TALER_KYCLOGIC_WebhookHandle
194 : {
195 :
196 : /**
197 : * Continuation to call when done.
198 : */
199 : TALER_KYCLOGIC_WebhookCallback cb;
200 :
201 : /**
202 : * Closure for @a cb.
203 : */
204 : void *cb_cls;
205 :
206 : /**
207 : * Task for asynchronous execution.
208 : */
209 : struct GNUNET_SCHEDULER_Task *task;
210 :
211 : /**
212 : * Overall plugin state.
213 : */
214 : struct PluginState *ps;
215 :
216 : /**
217 : * Our configuration details.
218 : */
219 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
220 :
221 : /**
222 : * Connection we are handling.
223 : */
224 : struct MHD_Connection *connection;
225 :
226 : /**
227 : * Verification ID from the service.
228 : */
229 : char *verification_id;
230 :
231 : /**
232 : * Applicant ID from the service.
233 : */
234 : char *applicant_id;
235 :
236 : /**
237 : * URL of the cURL request.
238 : */
239 : char *url;
240 :
241 : /**
242 : * Handle for the request.
243 : */
244 : struct GNUNET_CURL_Job *job;
245 :
246 : /**
247 : * Response to return asynchronously.
248 : */
249 : struct MHD_Response *resp;
250 :
251 : /**
252 : * Our account ID.
253 : */
254 : struct TALER_PaytoHashP h_payto;
255 :
256 : /**
257 : * Row in legitimizations for the given
258 : * @e verification_id.
259 : */
260 : uint64_t process_row;
261 :
262 : /**
263 : * HTTP response code to return asynchronously.
264 : */
265 : unsigned int response_code;
266 : };
267 :
268 :
269 : /**
270 : * Release configuration resources previously loaded
271 : *
272 : * @param[in] pd configuration to release
273 : */
274 : static void
275 0 : kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
276 : {
277 0 : curl_slist_free_all (pd->slist);
278 0 : GNUNET_free (pd->auth_token);
279 0 : GNUNET_free (pd->form_id);
280 0 : GNUNET_free (pd->section);
281 0 : GNUNET_free (pd);
282 0 : }
283 :
284 :
285 : /**
286 : * Load the configuration of the KYC provider.
287 : *
288 : * @param cls closure
289 : * @param provider_section_name configuration section to parse
290 : * @return NULL if configuration is invalid
291 : */
292 : static struct TALER_KYCLOGIC_ProviderDetails *
293 1 : kycaid_load_configuration (void *cls,
294 : const char *provider_section_name)
295 : {
296 1 : struct PluginState *ps = cls;
297 : struct TALER_KYCLOGIC_ProviderDetails *pd;
298 :
299 1 : pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
300 1 : pd->ps = ps;
301 1 : pd->section = GNUNET_strdup (provider_section_name);
302 1 : if (GNUNET_OK !=
303 1 : GNUNET_CONFIGURATION_get_value_time (ps->cfg,
304 : provider_section_name,
305 : "KYC_KYCAID_VALIDITY",
306 : &pd->validity))
307 : {
308 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
309 : provider_section_name,
310 : "KYC_KYCAID_VALIDITY");
311 0 : kycaid_unload_configuration (pd);
312 0 : return NULL;
313 : }
314 1 : if (GNUNET_OK !=
315 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
316 : provider_section_name,
317 : "KYC_KYCAID_AUTH_TOKEN",
318 : &pd->auth_token))
319 : {
320 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
321 : provider_section_name,
322 : "KYC_KYCAID_AUTH_TOKEN");
323 0 : kycaid_unload_configuration (pd);
324 0 : return NULL;
325 : }
326 1 : if (GNUNET_OK !=
327 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
328 : provider_section_name,
329 : "KYC_KYCAID_FORM_ID",
330 : &pd->form_id))
331 : {
332 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
333 : provider_section_name,
334 : "KYC_KYCAID_FORM_ID");
335 0 : kycaid_unload_configuration (pd);
336 0 : return NULL;
337 : }
338 : {
339 : char *auth;
340 :
341 1 : GNUNET_asprintf (&auth,
342 : "%s: Token %s",
343 : MHD_HTTP_HEADER_AUTHORIZATION,
344 : pd->auth_token);
345 1 : pd->slist = curl_slist_append (NULL,
346 : auth);
347 1 : GNUNET_free (auth);
348 : }
349 1 : return pd;
350 : }
351 :
352 :
353 : /**
354 : * Cancel KYC check initiation.
355 : *
356 : * @param[in] ih handle of operation to cancel
357 : */
358 : static void
359 0 : kycaid_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
360 : {
361 0 : if (NULL != ih->job)
362 : {
363 0 : GNUNET_CURL_job_cancel (ih->job);
364 0 : ih->job = NULL;
365 : }
366 0 : GNUNET_free (ih->url);
367 0 : TALER_curl_easy_post_finished (&ih->ctx);
368 0 : GNUNET_free (ih);
369 0 : }
370 :
371 :
372 : /**
373 : * Function called when we're done processing the
374 : * HTTP "/forms/{form_id}/urls" request.
375 : *
376 : * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
377 : * @param response_code HTTP response code, 0 on error
378 : * @param response parsed JSON result, NULL on error
379 : */
380 : static void
381 0 : handle_initiate_finished (void *cls,
382 : long response_code,
383 : const void *response)
384 : {
385 0 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
386 0 : const json_t *j = response;
387 :
388 0 : ih->job = NULL;
389 0 : switch (response_code)
390 : {
391 0 : case MHD_HTTP_OK:
392 : {
393 : const char *verification_id;
394 : const char *form_url;
395 : struct GNUNET_JSON_Specification spec[] = {
396 0 : GNUNET_JSON_spec_string ("verification_id",
397 : &verification_id),
398 0 : GNUNET_JSON_spec_string ("form_url",
399 : &form_url),
400 0 : GNUNET_JSON_spec_end ()
401 : };
402 :
403 0 : if (GNUNET_OK !=
404 0 : GNUNET_JSON_parse (j,
405 : spec,
406 : NULL, NULL))
407 : {
408 0 : GNUNET_break_op (0);
409 0 : json_dumpf (j,
410 : stderr,
411 : JSON_INDENT (2));
412 0 : ih->cb (ih->cb_cls,
413 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
414 : NULL,
415 : NULL,
416 : NULL,
417 0 : json_string_value (json_object_get (j,
418 : "type")));
419 0 : break;
420 : }
421 0 : ih->cb (ih->cb_cls,
422 : TALER_EC_NONE,
423 : form_url,
424 : NULL, /* no provider_user_id */
425 : verification_id,
426 : NULL /* no error */);
427 0 : GNUNET_JSON_parse_free (spec);
428 : }
429 0 : break;
430 0 : case MHD_HTTP_BAD_REQUEST:
431 : case MHD_HTTP_NOT_FOUND:
432 : case MHD_HTTP_CONFLICT:
433 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
434 : "KYCAID failed with response %u:\n",
435 : (unsigned int) response_code);
436 0 : json_dumpf (j,
437 : stderr,
438 : JSON_INDENT (2));
439 0 : ih->cb (ih->cb_cls,
440 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
441 : NULL,
442 : NULL,
443 : NULL,
444 0 : json_string_value (json_object_get (j,
445 : "type")));
446 0 : break;
447 0 : case MHD_HTTP_UNAUTHORIZED:
448 : case MHD_HTTP_PAYMENT_REQUIRED:
449 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
450 : "Refused access with HTTP status code %u\n",
451 : (unsigned int) response_code);
452 0 : ih->cb (ih->cb_cls,
453 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED,
454 : NULL,
455 : NULL,
456 : NULL,
457 0 : json_string_value (json_object_get (j,
458 : "type")));
459 0 : break;
460 0 : case MHD_HTTP_REQUEST_TIMEOUT:
461 0 : ih->cb (ih->cb_cls,
462 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT,
463 : NULL,
464 : NULL,
465 : NULL,
466 0 : json_string_value (json_object_get (j,
467 : "type")));
468 0 : break;
469 0 : case MHD_HTTP_UNPROCESSABLE_ENTITY: /* validation */
470 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
471 : "KYCAID failed with response %u:\n",
472 : (unsigned int) response_code);
473 0 : json_dumpf (j,
474 : stderr,
475 : JSON_INDENT (2));
476 0 : ih->cb (ih->cb_cls,
477 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
478 : NULL,
479 : NULL,
480 : NULL,
481 0 : json_string_value (json_object_get (j,
482 : "type")));
483 0 : break;
484 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
485 0 : ih->cb (ih->cb_cls,
486 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
487 : NULL,
488 : NULL,
489 : NULL,
490 0 : json_string_value (json_object_get (j,
491 : "type")));
492 0 : break;
493 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
494 0 : ih->cb (ih->cb_cls,
495 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
496 : NULL,
497 : NULL,
498 : NULL,
499 0 : json_string_value (json_object_get (j,
500 : "type")));
501 0 : break;
502 0 : default:
503 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
504 : "Unexpected KYCAID response %u:\n",
505 : (unsigned int) response_code);
506 0 : json_dumpf (j,
507 : stderr,
508 : JSON_INDENT (2));
509 0 : ih->cb (ih->cb_cls,
510 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
511 : NULL,
512 : NULL,
513 : NULL,
514 0 : json_string_value (json_object_get (j,
515 : "type")));
516 0 : break;
517 : }
518 0 : kycaid_initiate_cancel (ih);
519 0 : }
520 :
521 :
522 : /**
523 : * Initiate KYC check.
524 : *
525 : * @param cls the @e cls of this struct with the plugin-specific state
526 : * @param pd provider configuration details
527 : * @param account_id which account to trigger process for
528 : * @param legitimization_uuid unique ID for the legitimization process
529 : * @param cb function to call with the result
530 : * @param cb_cls closure for @a cb
531 : * @return handle to cancel operation early
532 : */
533 : static struct TALER_KYCLOGIC_InitiateHandle *
534 0 : kycaid_initiate (void *cls,
535 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
536 : const struct TALER_PaytoHashP *account_id,
537 : uint64_t legitimization_uuid,
538 : TALER_KYCLOGIC_InitiateCallback cb,
539 : void *cb_cls)
540 : {
541 0 : struct PluginState *ps = cls;
542 : struct TALER_KYCLOGIC_InitiateHandle *ih;
543 : json_t *body;
544 : CURL *eh;
545 :
546 0 : eh = curl_easy_init ();
547 0 : if (NULL == eh)
548 : {
549 0 : GNUNET_break (0);
550 0 : return NULL;
551 : }
552 0 : ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
553 0 : ih->legitimization_uuid = legitimization_uuid;
554 0 : ih->cb = cb;
555 0 : ih->cb_cls = cb_cls;
556 0 : ih->h_payto = *account_id;
557 0 : ih->pd = pd;
558 0 : GNUNET_asprintf (&ih->url,
559 : "https://api.kycaid.com/forms/%s/urls",
560 : pd->form_id);
561 0 : body = GNUNET_JSON_PACK (
562 : GNUNET_JSON_pack_data64_auto ("external_applicant_id",
563 : account_id)
564 : );
565 0 : GNUNET_break (CURLE_OK ==
566 : curl_easy_setopt (eh,
567 : CURLOPT_VERBOSE,
568 : 0));
569 0 : GNUNET_assert (CURLE_OK ==
570 : curl_easy_setopt (eh,
571 : CURLOPT_MAXREDIRS,
572 : 1L));
573 0 : GNUNET_break (CURLE_OK ==
574 : curl_easy_setopt (eh,
575 : CURLOPT_URL,
576 : ih->url));
577 0 : if (GNUNET_OK !=
578 0 : TALER_curl_easy_post (&ih->ctx,
579 : eh,
580 : body))
581 : {
582 0 : GNUNET_break (0);
583 0 : GNUNET_free (ih->url);
584 0 : GNUNET_free (ih);
585 0 : curl_easy_cleanup (eh);
586 0 : json_decref (body);
587 0 : return NULL;
588 : }
589 0 : ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
590 : eh,
591 0 : ih->ctx.headers,
592 : &handle_initiate_finished,
593 : ih);
594 0 : GNUNET_CURL_extend_headers (ih->job,
595 0 : pd->slist);
596 0 : return ih;
597 : }
598 :
599 :
600 : /**
601 : * Cancel KYC proof.
602 : *
603 : * @param[in] ph handle of operation to cancel
604 : */
605 : static void
606 0 : kycaid_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
607 : {
608 0 : if (NULL != ph->task)
609 : {
610 0 : GNUNET_SCHEDULER_cancel (ph->task);
611 0 : ph->task = NULL;
612 : }
613 0 : GNUNET_free (ph);
614 0 : }
615 :
616 :
617 : /**
618 : * Call @a ph callback with HTTP error response.
619 : *
620 : * @param cls proof handle to generate reply for
621 : */
622 : static void
623 0 : proof_reply (void *cls)
624 : {
625 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
626 : struct MHD_Response *resp;
627 :
628 0 : resp = TALER_MHD_make_error (TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
629 : "there is no '/kyc-proof' for kycaid");
630 0 : ph->cb (ph->cb_cls,
631 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
632 : NULL, /* user id */
633 : NULL, /* provider legi ID */
634 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
635 : MHD_HTTP_BAD_REQUEST,
636 : resp);
637 0 : }
638 :
639 :
640 : /**
641 : * Check KYC status and return status to human. Not
642 : * used by KYC AID!
643 : *
644 : * @param cls the @e cls of this struct with the plugin-specific state
645 : * @param pd provider configuration details
646 : * @param url_path rest of the URL after `/kyc-webhook/`
647 : * @param connection MHD connection object (for HTTP headers)
648 : * @param account_id which account to trigger process for
649 : * @param process_row row in the legitimization processes table the legitimization is for
650 : * @param provider_user_id user ID (or NULL) the proof is for
651 : * @param provider_legitimization_id legitimization ID the proof is for
652 : * @param cb function to call with the result
653 : * @param cb_cls closure for @a cb
654 : * @return handle to cancel operation early
655 : */
656 : static struct TALER_KYCLOGIC_ProofHandle *
657 0 : kycaid_proof (void *cls,
658 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
659 : const char *const url_path[],
660 : struct MHD_Connection *connection,
661 : const struct TALER_PaytoHashP *account_id,
662 : uint64_t process_row,
663 : const char *provider_user_id,
664 : const char *provider_legitimization_id,
665 : TALER_KYCLOGIC_ProofCallback cb,
666 : void *cb_cls)
667 : {
668 0 : struct PluginState *ps = cls;
669 : struct TALER_KYCLOGIC_ProofHandle *ph;
670 :
671 0 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
672 0 : ph->ps = ps;
673 0 : ph->pd = pd;
674 0 : ph->cb = cb;
675 0 : ph->cb_cls = cb_cls;
676 0 : ph->connection = connection;
677 0 : ph->task = GNUNET_SCHEDULER_add_now (&proof_reply,
678 : ph);
679 0 : return ph;
680 : }
681 :
682 :
683 : /**
684 : * Cancel KYC webhook execution.
685 : *
686 : * @param[in] wh handle of operation to cancel
687 : */
688 : static void
689 0 : kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
690 : {
691 0 : if (NULL != wh->task)
692 : {
693 0 : GNUNET_SCHEDULER_cancel (wh->task);
694 0 : wh->task = NULL;
695 : }
696 0 : if (NULL != wh->job)
697 : {
698 0 : GNUNET_CURL_job_cancel (wh->job);
699 0 : wh->job = NULL;
700 : }
701 0 : GNUNET_free (wh->verification_id);
702 0 : GNUNET_free (wh->applicant_id);
703 0 : GNUNET_free (wh->url);
704 0 : GNUNET_free (wh);
705 0 : }
706 :
707 :
708 : /**
709 : * Extract KYC failure reasons and log those
710 : *
711 : * @param verifications JSON object with failure details
712 : */
713 : static void
714 0 : log_failure (json_t *verifications)
715 : {
716 : json_t *member;
717 : const char *name;
718 0 : json_object_foreach (verifications, name, member)
719 : {
720 : bool iverified;
721 : const char *comment;
722 : struct GNUNET_JSON_Specification spec[] = {
723 0 : GNUNET_JSON_spec_bool ("verified",
724 : &iverified),
725 0 : GNUNET_JSON_spec_string ("comment",
726 : &comment),
727 0 : GNUNET_JSON_spec_end ()
728 : };
729 :
730 0 : if (GNUNET_OK !=
731 0 : GNUNET_JSON_parse (member,
732 : spec,
733 : NULL, NULL))
734 : {
735 0 : GNUNET_break_op (0);
736 0 : json_dumpf (member,
737 : stderr,
738 : JSON_INDENT (2));
739 0 : continue;
740 : }
741 0 : if (iverified)
742 0 : continue;
743 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
744 : "KYC verification of attribute `%s' failed: %s\n",
745 : name,
746 : comment);
747 : }
748 0 : }
749 :
750 :
751 : /**
752 : * Function called when we're done processing the
753 : * HTTP "/verifications/{verification_id}" request.
754 : *
755 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
756 : * @param response_code HTTP response code, 0 on error
757 : * @param response parsed JSON result, NULL on error
758 : */
759 : static void
760 0 : handle_webhook_finished (void *cls,
761 : long response_code,
762 : const void *response)
763 : {
764 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
765 0 : const json_t *j = response;
766 : struct MHD_Response *resp;
767 :
768 0 : wh->job = NULL;
769 0 : switch (response_code)
770 : {
771 0 : case MHD_HTTP_OK:
772 : {
773 : const char *applicant_id;
774 : const char *verification_id;
775 : const char *status;
776 : bool verified;
777 : json_t *verifications;
778 : struct GNUNET_JSON_Specification spec[] = {
779 0 : GNUNET_JSON_spec_string ("applicant_id",
780 : &applicant_id),
781 0 : GNUNET_JSON_spec_string ("verification_id",
782 : &verification_id),
783 0 : GNUNET_JSON_spec_string ("status",
784 : &status), /* completed, pending, ... */
785 0 : GNUNET_JSON_spec_bool ("verified",
786 : &verified),
787 0 : GNUNET_JSON_spec_json ("verifications",
788 : &verifications),
789 0 : GNUNET_JSON_spec_end ()
790 : };
791 : struct GNUNET_TIME_Absolute expiration;
792 :
793 0 : if (GNUNET_OK !=
794 0 : GNUNET_JSON_parse (j,
795 : spec,
796 : NULL, NULL))
797 : {
798 0 : GNUNET_break_op (0);
799 0 : json_dumpf (j,
800 : stderr,
801 : JSON_INDENT (2));
802 0 : resp = TALER_MHD_MAKE_JSON_PACK (
803 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
804 : response_code),
805 : GNUNET_JSON_pack_object_incref ("kycaid_body",
806 : (json_t *) j));
807 0 : wh->cb (wh->cb_cls,
808 : wh->process_row,
809 0 : &wh->h_payto,
810 0 : wh->pd->section,
811 0 : wh->applicant_id,
812 0 : wh->verification_id,
813 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
814 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
815 : MHD_HTTP_BAD_GATEWAY,
816 : resp);
817 0 : break;
818 : }
819 0 : if (! verified)
820 : {
821 0 : log_failure (verifications);
822 : }
823 0 : resp = MHD_create_response_from_buffer (0,
824 : "",
825 : MHD_RESPMEM_PERSISTENT);
826 0 : if (verified)
827 : {
828 0 : expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
829 0 : wh->cb (wh->cb_cls,
830 : wh->process_row,
831 0 : &wh->h_payto,
832 0 : wh->pd->section,
833 0 : wh->applicant_id,
834 0 : wh->verification_id,
835 : TALER_KYCLOGIC_STATUS_SUCCESS,
836 : expiration,
837 : MHD_HTTP_NO_CONTENT,
838 : resp);
839 : }
840 : else
841 : {
842 0 : wh->cb (wh->cb_cls,
843 : wh->process_row,
844 0 : &wh->h_payto,
845 0 : wh->pd->section,
846 0 : wh->applicant_id,
847 0 : wh->verification_id,
848 : TALER_KYCLOGIC_STATUS_USER_ABORTED,
849 0 : GNUNET_TIME_UNIT_ZERO_ABS,
850 : MHD_HTTP_NO_CONTENT,
851 : resp);
852 : }
853 0 : GNUNET_JSON_parse_free (spec);
854 : }
855 0 : break;
856 0 : case MHD_HTTP_BAD_REQUEST:
857 : case MHD_HTTP_NOT_FOUND:
858 : case MHD_HTTP_CONFLICT:
859 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
860 : "KYCAID failed with response %u:\n",
861 : (unsigned int) response_code);
862 0 : json_dumpf (j,
863 : stderr,
864 : JSON_INDENT (2));
865 0 : resp = TALER_MHD_MAKE_JSON_PACK (
866 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
867 : response_code));
868 0 : wh->cb (wh->cb_cls,
869 : wh->process_row,
870 0 : &wh->h_payto,
871 0 : wh->pd->section,
872 0 : wh->applicant_id,
873 0 : wh->verification_id,
874 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
875 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
876 : MHD_HTTP_INTERNAL_SERVER_ERROR,
877 : resp);
878 0 : break;
879 0 : case MHD_HTTP_UNAUTHORIZED:
880 : case MHD_HTTP_PAYMENT_REQUIRED:
881 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
882 : "Refused access with HTTP status code %u\n",
883 : (unsigned int) response_code);
884 0 : resp = TALER_MHD_MAKE_JSON_PACK (
885 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
886 : response_code),
887 : GNUNET_JSON_pack_object_incref ("kycaid_body",
888 : (json_t *) j));
889 0 : wh->cb (wh->cb_cls,
890 : wh->process_row,
891 0 : &wh->h_payto,
892 0 : wh->pd->section,
893 0 : wh->applicant_id,
894 0 : wh->verification_id,
895 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
896 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
897 : MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
898 : resp);
899 0 : break;
900 0 : case MHD_HTTP_REQUEST_TIMEOUT:
901 0 : resp = TALER_MHD_MAKE_JSON_PACK (
902 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
903 : response_code),
904 : GNUNET_JSON_pack_object_incref ("kycaid_body",
905 : (json_t *) j));
906 0 : wh->cb (wh->cb_cls,
907 : wh->process_row,
908 0 : &wh->h_payto,
909 0 : wh->pd->section,
910 0 : wh->applicant_id,
911 0 : wh->verification_id,
912 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
913 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
914 : MHD_HTTP_GATEWAY_TIMEOUT,
915 : resp);
916 0 : break;
917 0 : case MHD_HTTP_UNPROCESSABLE_ENTITY: /* validation */
918 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
919 : "KYCAID failed with response %u:\n",
920 : (unsigned int) response_code);
921 0 : json_dumpf (j,
922 : stderr,
923 : JSON_INDENT (2));
924 0 : resp = TALER_MHD_MAKE_JSON_PACK (
925 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
926 : response_code),
927 : GNUNET_JSON_pack_object_incref ("kycaid_body",
928 : (json_t *) j));
929 0 : wh->cb (wh->cb_cls,
930 : wh->process_row,
931 0 : &wh->h_payto,
932 0 : wh->pd->section,
933 0 : wh->applicant_id,
934 0 : wh->verification_id,
935 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
936 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
937 : MHD_HTTP_BAD_GATEWAY,
938 : resp);
939 0 : break;
940 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
941 0 : resp = TALER_MHD_MAKE_JSON_PACK (
942 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
943 : response_code),
944 : GNUNET_JSON_pack_object_incref ("kycaid_body",
945 : (json_t *) j));
946 0 : wh->cb (wh->cb_cls,
947 : wh->process_row,
948 0 : &wh->h_payto,
949 0 : wh->pd->section,
950 0 : wh->applicant_id,
951 0 : wh->verification_id,
952 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
953 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
954 : MHD_HTTP_SERVICE_UNAVAILABLE,
955 : resp);
956 0 : break;
957 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
958 0 : resp = TALER_MHD_MAKE_JSON_PACK (
959 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
960 : response_code),
961 : GNUNET_JSON_pack_object_incref ("kycaid_body",
962 : (json_t *) j));
963 0 : wh->cb (wh->cb_cls,
964 : wh->process_row,
965 0 : &wh->h_payto,
966 0 : wh->pd->section,
967 0 : wh->applicant_id,
968 0 : wh->verification_id,
969 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
970 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
971 : MHD_HTTP_BAD_GATEWAY,
972 : resp);
973 0 : break;
974 0 : default:
975 0 : resp = TALER_MHD_MAKE_JSON_PACK (
976 : GNUNET_JSON_pack_uint64 ("kycaid_http_status",
977 : response_code),
978 : GNUNET_JSON_pack_object_incref ("kycaid_body",
979 : (json_t *) j));
980 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
981 : "Unexpected KYCAID response %u:\n",
982 : (unsigned int) response_code);
983 0 : json_dumpf (j,
984 : stderr,
985 : JSON_INDENT (2));
986 0 : wh->cb (wh->cb_cls,
987 : wh->process_row,
988 0 : &wh->h_payto,
989 0 : wh->pd->section,
990 0 : wh->applicant_id,
991 0 : wh->verification_id,
992 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
993 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
994 : MHD_HTTP_BAD_GATEWAY,
995 : resp);
996 0 : break;
997 : }
998 0 : kycaid_webhook_cancel (wh);
999 0 : }
1000 :
1001 :
1002 : /**
1003 : * Asynchronously return a reply for the webhook.
1004 : *
1005 : * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
1006 : */
1007 : static void
1008 0 : async_webhook_reply (void *cls)
1009 : {
1010 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1011 :
1012 0 : wh->cb (wh->cb_cls,
1013 : wh->process_row,
1014 0 : (0 == wh->process_row)
1015 : ? NULL
1016 : : &wh->h_payto,
1017 0 : wh->pd->section,
1018 0 : wh->applicant_id, /* provider user ID */
1019 0 : wh->verification_id, /* provider legi ID */
1020 : TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
1021 0 : GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
1022 : wh->response_code,
1023 : wh->resp);
1024 0 : kycaid_webhook_cancel (wh);
1025 0 : }
1026 :
1027 :
1028 : /**
1029 : * Check KYC status and return result for Webhook. We do NOT implement the
1030 : * authentication check proposed by the KYCAID documentation, as it would
1031 : * allow an attacker who learns the access token to easily bypass the KYC
1032 : * checks. Instead, we insist on explicitly requesting the KYC status from the
1033 : * provider (at least on success).
1034 : *
1035 : * @param cls the @e cls of this struct with the plugin-specific state
1036 : * @param pd provider configuration details
1037 : * @param plc callback to lookup accounts with
1038 : * @param plc_cls closure for @a plc
1039 : * @param http_method HTTP method used for the webhook
1040 : * @param url_path rest of the URL after `/kyc-webhook/`
1041 : * @param connection MHD connection object (for HTTP headers)
1042 : * @param body HTTP request body
1043 : * @param cb function to call with the result
1044 : * @param cb_cls closure for @a cb
1045 : * @return handle to cancel operation early
1046 : */
1047 : static struct TALER_KYCLOGIC_WebhookHandle *
1048 0 : kycaid_webhook (void *cls,
1049 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1050 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1051 : void *plc_cls,
1052 : const char *http_method,
1053 : const char *const url_path[],
1054 : struct MHD_Connection *connection,
1055 : const json_t *body,
1056 : TALER_KYCLOGIC_WebhookCallback cb,
1057 : void *cb_cls)
1058 : {
1059 0 : struct PluginState *ps = cls;
1060 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1061 : CURL *eh;
1062 : const char *request_id;
1063 : const char *type;
1064 : const char *verification_id;
1065 : const char *applicant_id;
1066 : const char *status;
1067 : bool verified;
1068 : json_t *verifications;
1069 : struct GNUNET_JSON_Specification spec[] = {
1070 0 : GNUNET_JSON_spec_string ("request_id",
1071 : &request_id),
1072 0 : GNUNET_JSON_spec_string ("type",
1073 : &type),
1074 0 : GNUNET_JSON_spec_string ("verification_id",
1075 : &verification_id),
1076 0 : GNUNET_JSON_spec_string ("applicant_id",
1077 : &applicant_id),
1078 0 : GNUNET_JSON_spec_string ("status",
1079 : &status),
1080 0 : GNUNET_JSON_spec_bool ("verified",
1081 : &verified),
1082 0 : GNUNET_JSON_spec_json ("verifications",
1083 : &verifications),
1084 0 : GNUNET_JSON_spec_end ()
1085 : };
1086 : enum GNUNET_DB_QueryStatus qs;
1087 :
1088 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1089 0 : wh->cb = cb;
1090 0 : wh->cb_cls = cb_cls;
1091 0 : wh->ps = ps;
1092 0 : wh->pd = pd;
1093 0 : wh->connection = connection;
1094 :
1095 0 : if (NULL == pd)
1096 : {
1097 0 : GNUNET_break_op (0);
1098 0 : json_dumpf (body,
1099 : stderr,
1100 : JSON_INDENT (2));
1101 0 : wh->resp = TALER_MHD_make_error (
1102 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
1103 : "kycaid");
1104 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
1105 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1106 : wh);
1107 0 : return wh;
1108 : }
1109 :
1110 0 : if (GNUNET_OK !=
1111 0 : GNUNET_JSON_parse (body,
1112 : spec,
1113 : NULL, NULL))
1114 : {
1115 0 : GNUNET_break_op (0);
1116 0 : json_dumpf (body,
1117 : stderr,
1118 : JSON_INDENT (2));
1119 0 : wh->resp = TALER_MHD_MAKE_JSON_PACK (
1120 : GNUNET_JSON_pack_object_incref ("webhook_body",
1121 : (json_t *) body));
1122 0 : wh->response_code = MHD_HTTP_BAD_REQUEST;
1123 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1124 : wh);
1125 0 : return wh;
1126 : }
1127 0 : qs = plc (plc_cls,
1128 0 : pd->section,
1129 : verification_id,
1130 : &wh->h_payto,
1131 : &wh->process_row);
1132 0 : if (qs < 0)
1133 : {
1134 0 : wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
1135 : "provider-legitimization-lookup");
1136 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1137 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1138 : wh);
1139 0 : GNUNET_JSON_parse_free (spec);
1140 0 : return wh;
1141 : }
1142 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1143 : {
1144 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1145 : "Received webhook for unknown verification ID `%s'\n",
1146 : verification_id);
1147 0 : wh->resp = TALER_MHD_make_error (
1148 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
1149 : verification_id);
1150 0 : wh->response_code = MHD_HTTP_NOT_FOUND;
1151 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1152 : wh);
1153 0 : GNUNET_JSON_parse_free (spec);
1154 0 : return wh;
1155 : }
1156 0 : wh->verification_id = GNUNET_strdup (verification_id);
1157 0 : wh->applicant_id = GNUNET_strdup (applicant_id);
1158 0 : if (! verified)
1159 : {
1160 : /* We don't need to re-confirm the failure by
1161 : asking the API again. */
1162 0 : log_failure (verifications);
1163 0 : wh->response_code = MHD_HTTP_NO_CONTENT;
1164 0 : wh->resp = MHD_create_response_from_buffer (0,
1165 : "",
1166 : MHD_RESPMEM_PERSISTENT);
1167 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1168 : wh);
1169 0 : GNUNET_JSON_parse_free (spec);
1170 0 : return wh;
1171 : }
1172 :
1173 0 : eh = curl_easy_init ();
1174 0 : if (NULL == eh)
1175 : {
1176 0 : GNUNET_break (0);
1177 0 : wh->resp = TALER_MHD_make_error (
1178 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1179 : NULL);
1180 0 : wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1181 0 : wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
1182 : wh);
1183 0 : GNUNET_JSON_parse_free (spec);
1184 0 : return wh;
1185 : }
1186 :
1187 0 : GNUNET_asprintf (&wh->url,
1188 : "https://api.kycaid.com/verifications/%s",
1189 : verification_id);
1190 0 : GNUNET_break (CURLE_OK ==
1191 : curl_easy_setopt (eh,
1192 : CURLOPT_VERBOSE,
1193 : 0));
1194 0 : GNUNET_assert (CURLE_OK ==
1195 : curl_easy_setopt (eh,
1196 : CURLOPT_MAXREDIRS,
1197 : 1L));
1198 0 : GNUNET_break (CURLE_OK ==
1199 : curl_easy_setopt (eh,
1200 : CURLOPT_URL,
1201 : wh->url));
1202 0 : wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
1203 : eh,
1204 0 : pd->slist,
1205 : &handle_webhook_finished,
1206 : wh);
1207 0 : GNUNET_JSON_parse_free (spec);
1208 0 : return wh;
1209 : }
1210 :
1211 :
1212 : /**
1213 : * Initialize kycaid logic plugin
1214 : *
1215 : * @param cls a configuration instance
1216 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
1217 : */
1218 : void *
1219 1 : libtaler_plugin_kyclogic_kycaid_init (void *cls)
1220 : {
1221 1 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
1222 : struct TALER_KYCLOGIC_Plugin *plugin;
1223 : struct PluginState *ps;
1224 :
1225 1 : ps = GNUNET_new (struct PluginState);
1226 1 : ps->cfg = cfg;
1227 1 : if (GNUNET_OK !=
1228 1 : GNUNET_CONFIGURATION_get_value_string (cfg,
1229 : "exchange",
1230 : "BASE_URL",
1231 : &ps->exchange_base_url))
1232 : {
1233 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1234 : "exchange",
1235 : "BASE_URL");
1236 0 : GNUNET_free (ps);
1237 0 : return NULL;
1238 : }
1239 :
1240 : ps->curl_ctx
1241 2 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1242 1 : &ps->curl_rc);
1243 1 : if (NULL == ps->curl_ctx)
1244 : {
1245 0 : GNUNET_break (0);
1246 0 : GNUNET_free (ps->exchange_base_url);
1247 0 : GNUNET_free (ps);
1248 0 : return NULL;
1249 : }
1250 1 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
1251 :
1252 1 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
1253 1 : plugin->cls = ps;
1254 : plugin->load_configuration
1255 1 : = &kycaid_load_configuration;
1256 : plugin->unload_configuration
1257 1 : = &kycaid_unload_configuration;
1258 : plugin->initiate
1259 1 : = &kycaid_initiate;
1260 : plugin->initiate_cancel
1261 1 : = &kycaid_initiate_cancel;
1262 : plugin->proof
1263 1 : = &kycaid_proof;
1264 : plugin->proof_cancel
1265 1 : = &kycaid_proof_cancel;
1266 : plugin->webhook
1267 1 : = &kycaid_webhook;
1268 : plugin->webhook_cancel
1269 1 : = &kycaid_webhook_cancel;
1270 1 : return plugin;
1271 : }
1272 :
1273 :
1274 : /**
1275 : * Unload authorization plugin
1276 : *
1277 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
1278 : * @return NULL (always)
1279 : */
1280 : void *
1281 0 : libtaler_plugin_kyclogic_kycaid_done (void *cls)
1282 : {
1283 0 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
1284 0 : struct PluginState *ps = plugin->cls;
1285 :
1286 0 : if (NULL != ps->curl_ctx)
1287 : {
1288 0 : GNUNET_CURL_fini (ps->curl_ctx);
1289 0 : ps->curl_ctx = NULL;
1290 : }
1291 0 : if (NULL != ps->curl_rc)
1292 : {
1293 0 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
1294 0 : ps->curl_rc = NULL;
1295 : }
1296 0 : GNUNET_free (ps->exchange_base_url);
1297 0 : GNUNET_free (ps);
1298 0 : GNUNET_free (plugin);
1299 0 : return NULL;
1300 : }
|