Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : Copyright (C) 2022-2024 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_oauth2.c
18 : * @brief oauth2.0 based authentication flow logic
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_kyclogic_plugin.h"
22 : #include "taler/taler_mhd_lib.h"
23 : #include "taler/taler_templating_lib.h"
24 : #include "taler/taler_curl_lib.h"
25 : #include "taler/taler_json_lib.h"
26 : #include <regex.h>
27 : #include "taler/taler_util.h"
28 :
29 : /**
30 : * Set to 1 to get extra-verbose, possibly privacy-sensitive
31 : * data in the logs.
32 : */
33 : #define DEBUG 0
34 :
35 : /**
36 : * Saves the state of a plugin.
37 : */
38 : struct PluginState
39 : {
40 :
41 : /**
42 : * Our global configuration.
43 : */
44 : const struct GNUNET_CONFIGURATION_Handle *cfg;
45 :
46 : /**
47 : * Our base URL.
48 : */
49 : char *exchange_base_url;
50 :
51 : /**
52 : * Context for CURL operations (useful to the event loop)
53 : */
54 : struct GNUNET_CURL_Context *curl_ctx;
55 :
56 : /**
57 : * Context for integrating @e curl_ctx with the
58 : * GNUnet event loop.
59 : */
60 : struct GNUNET_CURL_RescheduleContext *curl_rc;
61 :
62 : };
63 :
64 :
65 : /**
66 : * Keeps the plugin-specific state for
67 : * a given configuration section.
68 : */
69 : struct TALER_KYCLOGIC_ProviderDetails
70 : {
71 :
72 : /**
73 : * Overall plugin state.
74 : */
75 : struct PluginState *ps;
76 :
77 : /**
78 : * Configuration section that configured us.
79 : */
80 : char *section;
81 :
82 : /**
83 : * URL of the Challenger ``/setup`` endpoint for
84 : * approving address validations. NULL if not used.
85 : */
86 : char *setup_url;
87 :
88 : /**
89 : * URL of the OAuth2.0 endpoint for KYC checks.
90 : */
91 : char *authorize_url;
92 :
93 : /**
94 : * URL of the OAuth2.0 endpoint for KYC checks.
95 : * (token/auth)
96 : */
97 : char *token_url;
98 :
99 : /**
100 : * URL of the user info access endpoint.
101 : */
102 : char *info_url;
103 :
104 : /**
105 : * Our client ID for OAuth2.0.
106 : */
107 : char *client_id;
108 :
109 : /**
110 : * Our client secret for OAuth2.0.
111 : */
112 : char *client_secret;
113 :
114 : /**
115 : * OAuth2 scope, NULL if not used
116 : */
117 : char *scope;
118 :
119 : /**
120 : * Where to redirect clients after the
121 : * Web-based KYC process is done?
122 : */
123 : char *post_kyc_redirect_url;
124 :
125 : /**
126 : * Name of the program we use to convert outputs
127 : * from OAuth2 outputs into our JSON inputs.
128 : */
129 : char *conversion_binary;
130 :
131 : /**
132 : * Validity time for a successful KYC process.
133 : */
134 : struct GNUNET_TIME_Relative validity;
135 :
136 : /**
137 : * Set to true if we are operating in DEBUG
138 : * mode and may return private details in HTML
139 : * responses to make diagnostics easier.
140 : */
141 : bool debug_mode;
142 : };
143 :
144 :
145 : /**
146 : * Handle for an initiation operation.
147 : */
148 : struct TALER_KYCLOGIC_InitiateHandle
149 : {
150 :
151 : /**
152 : * Hash of the payto:// URI we are initiating
153 : * the KYC for.
154 : */
155 : struct TALER_NormalizedPaytoHashP h_payto;
156 :
157 : /**
158 : * UUID being checked.
159 : */
160 : uint64_t legitimization_uuid;
161 :
162 : /**
163 : * Our configuration details.
164 : */
165 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
166 :
167 : /**
168 : * The task for asynchronous response generation.
169 : */
170 : struct GNUNET_SCHEDULER_Task *task;
171 :
172 : /**
173 : * Handle for the OAuth 2.0 setup request.
174 : */
175 : struct GNUNET_CURL_Job *job;
176 :
177 : /**
178 : * Continuation to call.
179 : */
180 : TALER_KYCLOGIC_InitiateCallback cb;
181 :
182 : /**
183 : * Closure for @a cb.
184 : */
185 : void *cb_cls;
186 :
187 : /**
188 : * Initial address to pass to the KYC provider on ``/setup``.
189 : */
190 : json_t *initial_address;
191 :
192 : /**
193 : * Context for #TEH_curl_easy_post(). Keeps the data that must
194 : * persist for Curl to make the upload.
195 : */
196 : struct TALER_CURL_PostContext ctx;
197 :
198 : };
199 :
200 :
201 : /**
202 : * Handle for an KYC proof operation.
203 : */
204 : struct TALER_KYCLOGIC_ProofHandle
205 : {
206 :
207 : /**
208 : * Our configuration details.
209 : */
210 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
211 :
212 : /**
213 : * HTTP connection we are processing.
214 : */
215 : struct MHD_Connection *connection;
216 :
217 : /**
218 : * Handle to an external process that converts the
219 : * Persona response to our internal format.
220 : */
221 : struct TALER_JSON_ExternalConversion *ec;
222 :
223 : /**
224 : * Hash of the payto URI that this is about.
225 : */
226 : struct TALER_NormalizedPaytoHashP h_payto;
227 :
228 : /**
229 : * Continuation to call.
230 : */
231 : TALER_KYCLOGIC_ProofCallback cb;
232 :
233 : /**
234 : * Closure for @e cb.
235 : */
236 : void *cb_cls;
237 :
238 : /**
239 : * Curl request we are running to the OAuth 2.0 service.
240 : */
241 : CURL *eh;
242 :
243 : /**
244 : * Body for the @e eh POST request.
245 : */
246 : char *post_body;
247 :
248 : /**
249 : * KYC attributes returned about the user by the OAuth 2.0 server.
250 : */
251 : json_t *attributes;
252 :
253 : /**
254 : * Response to return.
255 : */
256 : struct MHD_Response *response;
257 :
258 : /**
259 : * The task for asynchronous response generation.
260 : */
261 : struct GNUNET_SCHEDULER_Task *task;
262 :
263 : /**
264 : * Handle for the OAuth 2.0 CURL request.
265 : */
266 : struct GNUNET_CURL_Job *job;
267 :
268 : /**
269 : * User ID to return, the 'id' from OAuth.
270 : */
271 : char *provider_user_id;
272 :
273 : /**
274 : * Legitimization ID to return, the 64-bit row ID
275 : * as a string.
276 : */
277 : char provider_legitimization_id[32];
278 :
279 : /**
280 : * KYC status to return.
281 : */
282 : enum TALER_KYCLOGIC_KycStatus status;
283 :
284 : /**
285 : * HTTP status to return.
286 : */
287 : unsigned int http_status;
288 :
289 :
290 : };
291 :
292 :
293 : /**
294 : * Handle for an KYC Web hook operation.
295 : */
296 : struct TALER_KYCLOGIC_WebhookHandle
297 : {
298 :
299 : /**
300 : * Continuation to call when done.
301 : */
302 : TALER_KYCLOGIC_WebhookCallback cb;
303 :
304 : /**
305 : * Closure for @a cb.
306 : */
307 : void *cb_cls;
308 :
309 : /**
310 : * Task for asynchronous execution.
311 : */
312 : struct GNUNET_SCHEDULER_Task *task;
313 :
314 : /**
315 : * Overall plugin state.
316 : */
317 : struct PluginState *ps;
318 : };
319 :
320 :
321 : /**
322 : * Release configuration resources previously loaded
323 : *
324 : * @param[in] pd configuration to release
325 : */
326 : static void
327 92 : oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
328 : {
329 92 : GNUNET_free (pd->section);
330 92 : GNUNET_free (pd->token_url);
331 92 : GNUNET_free (pd->setup_url);
332 92 : GNUNET_free (pd->authorize_url);
333 92 : GNUNET_free (pd->info_url);
334 92 : GNUNET_free (pd->client_id);
335 92 : GNUNET_free (pd->client_secret);
336 92 : GNUNET_free (pd->scope);
337 92 : GNUNET_free (pd->post_kyc_redirect_url);
338 92 : GNUNET_free (pd->conversion_binary);
339 92 : GNUNET_free (pd);
340 92 : }
341 :
342 :
343 : /**
344 : * Load the configuration of the KYC provider.
345 : *
346 : * @param cls closure
347 : * @param provider_section_name configuration section to parse
348 : * @return NULL if configuration is invalid
349 : */
350 : static struct TALER_KYCLOGIC_ProviderDetails *
351 92 : oauth2_load_configuration (void *cls,
352 : const char *provider_section_name)
353 : {
354 92 : struct PluginState *ps = cls;
355 : struct TALER_KYCLOGIC_ProviderDetails *pd;
356 : char *s;
357 :
358 92 : pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
359 92 : pd->ps = ps;
360 92 : pd->section = GNUNET_strdup (provider_section_name);
361 92 : if (GNUNET_OK !=
362 92 : GNUNET_CONFIGURATION_get_value_time (ps->cfg,
363 : provider_section_name,
364 : "KYC_OAUTH2_VALIDITY",
365 : &pd->validity))
366 : {
367 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
368 : provider_section_name,
369 : "KYC_OAUTH2_VALIDITY");
370 0 : oauth2_unload_configuration (pd);
371 0 : return NULL;
372 : }
373 :
374 92 : if (GNUNET_OK !=
375 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
376 : provider_section_name,
377 : "KYC_OAUTH2_CLIENT_ID",
378 : &s))
379 : {
380 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
381 : provider_section_name,
382 : "KYC_OAUTH2_CLIENT_ID");
383 0 : oauth2_unload_configuration (pd);
384 0 : return NULL;
385 : }
386 92 : pd->client_id = s;
387 :
388 92 : if (GNUNET_OK ==
389 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
390 : provider_section_name,
391 : "KYC_OAUTH2_SCOPE",
392 : &s))
393 : {
394 0 : pd->scope = s;
395 : }
396 :
397 92 : if (GNUNET_OK !=
398 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
399 : provider_section_name,
400 : "KYC_OAUTH2_TOKEN_URL",
401 : &s))
402 : {
403 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
404 : provider_section_name,
405 : "KYC_OAUTH2_TOKEN_URL");
406 0 : oauth2_unload_configuration (pd);
407 0 : return NULL;
408 : }
409 92 : if ( (! TALER_url_valid_charset (s)) ||
410 92 : ( (0 != strncasecmp (s,
411 : "http://",
412 61 : strlen ("http://"))) &&
413 61 : (0 != strncasecmp (s,
414 : "https://",
415 : strlen ("https://"))) ) )
416 : {
417 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
418 : provider_section_name,
419 : "KYC_OAUTH2_TOKEN_URL",
420 : "not a valid URL");
421 0 : GNUNET_free (s);
422 0 : oauth2_unload_configuration (pd);
423 0 : return NULL;
424 : }
425 92 : pd->token_url = s;
426 :
427 92 : if (GNUNET_OK !=
428 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
429 : provider_section_name,
430 : "KYC_OAUTH2_AUTHORIZE_URL",
431 : &s))
432 : {
433 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
434 : provider_section_name,
435 : "KYC_OAUTH2_AUTHORIZE_URL");
436 0 : oauth2_unload_configuration (pd);
437 0 : return NULL;
438 : }
439 92 : if ( (! TALER_url_valid_charset (s)) ||
440 92 : ( (0 != strncasecmp (s,
441 : "http://",
442 61 : strlen ("http://"))) &&
443 61 : (0 != strncasecmp (s,
444 : "https://",
445 : strlen ("https://"))) ) )
446 : {
447 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
448 : provider_section_name,
449 : "KYC_OAUTH2_AUTHORIZE_URL",
450 : "not a valid URL");
451 0 : oauth2_unload_configuration (pd);
452 0 : GNUNET_free (s);
453 0 : return NULL;
454 : }
455 92 : if (NULL != strchr (s, '#'))
456 : {
457 0 : const char *extra = strchr (s, '#');
458 0 : const char *slash = strrchr (s, '/');
459 :
460 0 : if ( (0 != strcasecmp (extra,
461 0 : "#setup")) ||
462 : (NULL == slash) )
463 : {
464 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
465 : provider_section_name,
466 : "KYC_OAUTH2_AUTHORIZE_URL",
467 : "not a valid authorze URL (bad fragment)");
468 0 : oauth2_unload_configuration (pd);
469 0 : GNUNET_free (s);
470 0 : return NULL;
471 : }
472 0 : pd->authorize_url = GNUNET_strndup (s,
473 : extra - s);
474 0 : GNUNET_asprintf (&pd->setup_url,
475 : "%.*s/setup/%s",
476 0 : (int) (slash - s),
477 : s,
478 : pd->client_id);
479 0 : GNUNET_free (s);
480 : }
481 : else
482 : {
483 92 : pd->authorize_url = s;
484 : }
485 :
486 92 : if (GNUNET_OK !=
487 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
488 : provider_section_name,
489 : "KYC_OAUTH2_INFO_URL",
490 : &s))
491 : {
492 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
493 : provider_section_name,
494 : "KYC_OAUTH2_INFO_URL");
495 0 : oauth2_unload_configuration (pd);
496 0 : return NULL;
497 : }
498 92 : if ( (! TALER_url_valid_charset (s)) ||
499 92 : ( (0 != strncasecmp (s,
500 : "http://",
501 61 : strlen ("http://"))) &&
502 61 : (0 != strncasecmp (s,
503 : "https://",
504 : strlen ("https://"))) ) )
505 : {
506 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
507 : provider_section_name,
508 : "KYC_INFO_URL",
509 : "not a valid URL");
510 0 : GNUNET_free (s);
511 0 : oauth2_unload_configuration (pd);
512 0 : return NULL;
513 : }
514 92 : pd->info_url = s;
515 :
516 92 : if (GNUNET_OK !=
517 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
518 : provider_section_name,
519 : "KYC_OAUTH2_CLIENT_SECRET",
520 : &s))
521 : {
522 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
523 : provider_section_name,
524 : "KYC_OAUTH2_CLIENT_SECRET");
525 0 : oauth2_unload_configuration (pd);
526 0 : return NULL;
527 : }
528 92 : pd->client_secret = s;
529 :
530 92 : if (GNUNET_OK !=
531 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
532 : provider_section_name,
533 : "KYC_OAUTH2_POST_URL",
534 : &s))
535 : {
536 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
537 : provider_section_name,
538 : "KYC_OAUTH2_POST_URL");
539 0 : oauth2_unload_configuration (pd);
540 0 : return NULL;
541 : }
542 92 : pd->post_kyc_redirect_url = s;
543 :
544 92 : if (GNUNET_OK !=
545 92 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
546 : provider_section_name,
547 : "KYC_OAUTH2_CONVERTER_HELPER",
548 : &pd->conversion_binary))
549 : {
550 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
551 : provider_section_name,
552 : "KYC_OAUTH2_CONVERTER_HELPER");
553 0 : oauth2_unload_configuration (pd);
554 0 : return NULL;
555 : }
556 92 : if (GNUNET_OK ==
557 92 : GNUNET_CONFIGURATION_get_value_yesno (ps->cfg,
558 : provider_section_name,
559 : "KYC_OAUTH2_DEBUG_MODE"))
560 0 : pd->debug_mode = true;
561 :
562 92 : return pd;
563 : }
564 :
565 :
566 : /**
567 : * Cancel KYC check initiation.
568 : *
569 : * @param[in] ih handle of operation to cancel
570 : */
571 : static void
572 10 : oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
573 : {
574 10 : if (NULL != ih->task)
575 : {
576 0 : GNUNET_SCHEDULER_cancel (ih->task);
577 0 : ih->task = NULL;
578 : }
579 10 : if (NULL != ih->job)
580 : {
581 0 : GNUNET_CURL_job_cancel (ih->job);
582 0 : ih->job = NULL;
583 : }
584 10 : TALER_curl_easy_post_finished (&ih->ctx);
585 10 : json_decref (ih->initial_address);
586 10 : GNUNET_free (ih);
587 10 : }
588 :
589 :
590 : /**
591 : * Logic to asynchronously return the response for
592 : * how to begin the OAuth2.0 checking process to
593 : * the client.
594 : *
595 : * @param ih process to redirect for
596 : * @param authorize_url authorization URL to use
597 : */
598 : static void
599 10 : initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
600 : const char *authorize_url)
601 : {
602 :
603 10 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
604 10 : struct PluginState *ps = pd->ps;
605 : char *hps;
606 : char *url;
607 : char legi_s[42];
608 :
609 10 : GNUNET_snprintf (legi_s,
610 : sizeof (legi_s),
611 : "%llu",
612 10 : (unsigned long long) ih->legitimization_uuid);
613 10 : hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
614 : sizeof (ih->h_payto));
615 : {
616 : char *redirect_uri_encoded;
617 :
618 : {
619 : char *redirect_uri;
620 :
621 10 : GNUNET_asprintf (&redirect_uri,
622 : "%skyc-proof/%s",
623 : ps->exchange_base_url,
624 10 : &pd->section[strlen ("kyc-provider-")]);
625 10 : redirect_uri_encoded = TALER_urlencode (redirect_uri);
626 10 : GNUNET_free (redirect_uri);
627 : }
628 10 : GNUNET_asprintf (&url,
629 : "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s",
630 : authorize_url,
631 10 : pd->client_id,
632 : redirect_uri_encoded,
633 : hps,
634 10 : NULL != pd->scope
635 : ? pd->scope
636 : : "");
637 10 : GNUNET_free (redirect_uri_encoded);
638 : }
639 10 : ih->cb (ih->cb_cls,
640 : TALER_EC_NONE,
641 : url,
642 : NULL /* unknown user_id here */,
643 : legi_s,
644 : NULL /* no error */);
645 10 : GNUNET_free (url);
646 10 : GNUNET_free (hps);
647 10 : oauth2_initiate_cancel (ih);
648 10 : }
649 :
650 :
651 : /**
652 : * After we are done with the CURL interaction we
653 : * need to update our database state with the information
654 : * retrieved.
655 : *
656 : * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
657 : * @param response_code HTTP response code from server, 0 on hard error
658 : * @param response in JSON, NULL if response was not in JSON format
659 : */
660 : static void
661 0 : handle_curl_setup_finished (void *cls,
662 : long response_code,
663 : const void *response)
664 : {
665 0 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
666 0 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
667 0 : const json_t *j = response;
668 :
669 0 : ih->job = NULL;
670 0 : switch (response_code)
671 : {
672 0 : case 0:
673 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
674 : "/setup URL failed to return HTTP response\n");
675 0 : ih->cb (ih->cb_cls,
676 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
677 : NULL,
678 : NULL,
679 : NULL,
680 : "/setup request to OAuth 2.0 backend returned no response");
681 0 : oauth2_initiate_cancel (ih);
682 0 : return;
683 0 : case MHD_HTTP_OK:
684 : {
685 : const char *nonce;
686 : struct GNUNET_JSON_Specification spec[] = {
687 0 : GNUNET_JSON_spec_string ("nonce",
688 : &nonce),
689 0 : GNUNET_JSON_spec_end ()
690 : };
691 : enum GNUNET_GenericReturnValue res;
692 : const char *emsg;
693 : unsigned int line;
694 : char *url;
695 :
696 0 : res = GNUNET_JSON_parse (j,
697 : spec,
698 : &emsg,
699 : &line);
700 0 : if (GNUNET_OK != res)
701 : {
702 0 : GNUNET_break_op (0);
703 0 : json_dumpf (j,
704 : stderr,
705 : JSON_INDENT (2));
706 0 : ih->cb (ih->cb_cls,
707 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
708 : NULL,
709 : NULL,
710 : NULL,
711 : "Unexpected response from KYC gateway: setup must return a nonce");
712 0 : oauth2_initiate_cancel (ih);
713 0 : return;
714 : }
715 0 : GNUNET_asprintf (&url,
716 : "%s/%s",
717 0 : pd->authorize_url,
718 : nonce);
719 0 : initiate_with_url (ih,
720 : url);
721 0 : GNUNET_free (url);
722 0 : return;
723 : }
724 : break;
725 0 : default:
726 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
727 : "/setup URL returned HTTP status %u\n",
728 : (unsigned int) response_code);
729 0 : ih->cb (ih->cb_cls,
730 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
731 : NULL,
732 : NULL,
733 : NULL,
734 : "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
735 0 : oauth2_initiate_cancel (ih);
736 0 : return;
737 : }
738 : }
739 :
740 :
741 : /**
742 : * Logic to asynchronously return the response for how to begin the OAuth2.0
743 : * checking process to the client. May first request a dynamic URL via
744 : * ``/setup`` if configured to use a client-authenticated setup process.
745 : *
746 : * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
747 : */
748 : static void
749 10 : initiate_task (void *cls)
750 : {
751 10 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
752 10 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
753 10 : struct PluginState *ps = pd->ps;
754 : CURL *eh;
755 :
756 10 : ih->task = NULL;
757 10 : if (NULL == pd->setup_url)
758 : {
759 10 : initiate_with_url (ih,
760 10 : pd->authorize_url);
761 10 : return;
762 : }
763 0 : eh = curl_easy_init ();
764 0 : if (NULL == eh)
765 : {
766 0 : GNUNET_break (0);
767 0 : ih->cb (ih->cb_cls,
768 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
769 : NULL,
770 : NULL,
771 : NULL,
772 : "curl_easy_init() failed");
773 0 : oauth2_initiate_cancel (ih);
774 0 : return;
775 : }
776 0 : GNUNET_assert (CURLE_OK ==
777 : curl_easy_setopt (eh,
778 : CURLOPT_URL,
779 : pd->setup_url));
780 : #if DEBUG
781 : GNUNET_assert (CURLE_OK ==
782 : curl_easy_setopt (eh,
783 : CURLOPT_VERBOSE,
784 : 1));
785 : #endif
786 0 : if (NULL == ih->initial_address)
787 : {
788 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
789 : "Staring OAuth 2.0 without initial address\n");
790 0 : GNUNET_assert (CURLE_OK ==
791 : curl_easy_setopt (eh,
792 : CURLOPT_POST,
793 : 1));
794 0 : GNUNET_assert (CURLE_OK ==
795 : curl_easy_setopt (eh,
796 : CURLOPT_POSTFIELDS,
797 : ""));
798 0 : GNUNET_assert (CURLE_OK ==
799 : curl_easy_setopt (eh,
800 : CURLOPT_POSTFIELDSIZE,
801 : (long) 0));
802 : }
803 : else
804 : {
805 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
806 : "Staring OAuth 2.0 with initial address\n");
807 : #if DEBUG
808 : json_dumpf (ih->initial_address,
809 : stderr,
810 : JSON_INDENT (2));
811 : fprintf (stderr,
812 : "\n");
813 : #endif
814 0 : if (GNUNET_OK !=
815 0 : TALER_curl_easy_post (&ih->ctx,
816 : eh,
817 0 : ih->initial_address))
818 : {
819 0 : curl_easy_cleanup (eh);
820 0 : ih->cb (ih->cb_cls,
821 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
822 : NULL,
823 : NULL,
824 : NULL,
825 : "TALER_curl_easy_post() failed");
826 0 : oauth2_initiate_cancel (ih);
827 0 : return;
828 : }
829 : }
830 0 : GNUNET_assert (CURLE_OK ==
831 : curl_easy_setopt (eh,
832 : CURLOPT_FOLLOWLOCATION,
833 : 1L));
834 0 : GNUNET_assert (CURLE_OK ==
835 : curl_easy_setopt (eh,
836 : CURLOPT_MAXREDIRS,
837 : 5L));
838 0 : ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
839 : eh,
840 0 : ih->ctx.headers,
841 : &handle_curl_setup_finished,
842 : ih);
843 : {
844 : char *hdr;
845 : struct curl_slist *slist;
846 :
847 0 : GNUNET_asprintf (&hdr,
848 : "%s: Bearer %s",
849 : MHD_HTTP_HEADER_AUTHORIZATION,
850 0 : pd->client_secret);
851 0 : slist = curl_slist_append (NULL,
852 : hdr);
853 0 : GNUNET_CURL_extend_headers (ih->job,
854 : slist);
855 0 : curl_slist_free_all (slist);
856 0 : GNUNET_free (hdr);
857 : }
858 : }
859 :
860 :
861 : /**
862 : * Initiate KYC check.
863 : *
864 : * @param cls the @e cls of this struct with the plugin-specific state
865 : * @param pd provider configuration details
866 : * @param account_id which account to trigger process for
867 : * @param legitimization_uuid unique ID for the legitimization process
868 : * @param context additional contextual information for the legi process
869 : * @param cb function to call with the result
870 : * @param cb_cls closure for @a cb
871 : * @return handle to cancel operation early
872 : */
873 : static struct TALER_KYCLOGIC_InitiateHandle *
874 10 : oauth2_initiate (void *cls,
875 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
876 : const struct TALER_NormalizedPaytoHashP *account_id,
877 : uint64_t legitimization_uuid,
878 : const json_t *context,
879 : TALER_KYCLOGIC_InitiateCallback cb,
880 : void *cb_cls)
881 : {
882 : struct TALER_KYCLOGIC_InitiateHandle *ih;
883 :
884 : (void) cls;
885 10 : ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
886 10 : ih->legitimization_uuid = legitimization_uuid;
887 10 : ih->cb = cb;
888 10 : ih->cb_cls = cb_cls;
889 10 : ih->h_payto = *account_id;
890 10 : ih->pd = pd;
891 10 : ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
892 : ih);
893 10 : if (NULL != context)
894 : {
895 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
896 : "Initiating OAuth2 validation with context\n");
897 : #if DEBUG
898 : json_dumpf (context,
899 : stderr,
900 : JSON_INDENT (2));
901 : fprintf (stderr,
902 : "\n");
903 : #endif
904 10 : ih->initial_address = json_incref (json_object_get (context,
905 : "initial_address"));
906 : }
907 10 : return ih;
908 : }
909 :
910 :
911 : /**
912 : * Cancel KYC proof.
913 : *
914 : * @param[in] ph handle of operation to cancel
915 : */
916 : static void
917 11 : oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
918 : {
919 11 : if (NULL != ph->ec)
920 : {
921 0 : TALER_JSON_external_conversion_stop (ph->ec);
922 0 : ph->ec = NULL;
923 : }
924 11 : if (NULL != ph->task)
925 : {
926 0 : GNUNET_SCHEDULER_cancel (ph->task);
927 0 : ph->task = NULL;
928 : }
929 11 : if (NULL != ph->job)
930 : {
931 0 : GNUNET_CURL_job_cancel (ph->job);
932 0 : ph->job = NULL;
933 : }
934 11 : if (NULL != ph->response)
935 : {
936 0 : MHD_destroy_response (ph->response);
937 0 : ph->response = NULL;
938 : }
939 11 : GNUNET_free (ph->provider_user_id);
940 11 : if (NULL != ph->attributes)
941 9 : json_decref (ph->attributes);
942 11 : GNUNET_free (ph->post_body);
943 11 : GNUNET_free (ph);
944 11 : }
945 :
946 :
947 : /**
948 : * Function called to asynchronously return the final
949 : * result to the callback.
950 : *
951 : * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
952 : */
953 : static void
954 11 : return_proof_response (void *cls)
955 : {
956 11 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
957 : const char *provider_name;
958 :
959 11 : ph->task = NULL;
960 11 : provider_name = ph->pd->section;
961 11 : if (0 !=
962 11 : strncasecmp (provider_name,
963 : "KYC-PROVIDER-",
964 : strlen ("KYC-PROVIDER-")))
965 : {
966 0 : GNUNET_break (0);
967 : }
968 : else
969 : {
970 11 : provider_name += strlen ("KYC-PROVIDER-");
971 : }
972 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
973 : "Returning KYC proof from `%s'\n",
974 : provider_name);
975 22 : ph->cb (ph->cb_cls,
976 : ph->status,
977 : provider_name,
978 11 : ph->provider_user_id,
979 11 : ph->provider_legitimization_id,
980 11 : GNUNET_TIME_relative_to_absolute (ph->pd->validity),
981 11 : ph->attributes,
982 : ph->http_status,
983 : ph->response);
984 11 : ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
985 11 : oauth2_proof_cancel (ph);
986 11 : }
987 :
988 :
989 : /**
990 : * Load a @a template and substitute using @a root, returning the result in a
991 : * @a reply encoded suitable for the @a connection with the given @a
992 : * http_status code. On errors, the @a http_status code
993 : * is updated to reflect the type of error encoded in the
994 : * @a reply.
995 : *
996 : * @param connection the connection we act upon
997 : * @param[in,out] http_status code to use on success,
998 : * set to alternative code on failure
999 : * @param template basename of the template to load
1000 : * @param root JSON object to pass as the root context
1001 : * @param[out] reply where to write the response object
1002 : * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
1003 : * #GNUNET_SYSERR on failure (to queue an error)
1004 : */
1005 : static enum GNUNET_GenericReturnValue
1006 2 : templating_build (struct MHD_Connection *connection,
1007 : unsigned int *http_status,
1008 : const char *template,
1009 : const json_t *root,
1010 : struct MHD_Response **reply)
1011 : {
1012 : enum GNUNET_GenericReturnValue ret;
1013 :
1014 2 : ret = TALER_TEMPLATING_build (connection,
1015 : http_status,
1016 : template,
1017 : NULL,
1018 : NULL,
1019 : root,
1020 : reply);
1021 2 : if (GNUNET_SYSERR != ret)
1022 : {
1023 2 : GNUNET_break (MHD_NO !=
1024 : MHD_add_response_header (*reply,
1025 : MHD_HTTP_HEADER_CONTENT_TYPE,
1026 : "text/html"));
1027 : }
1028 2 : return ret;
1029 : }
1030 :
1031 :
1032 : /**
1033 : * The request for @a ph failed. We may have gotten a useful error
1034 : * message in @a j. Generate a failure response.
1035 : *
1036 : * @param[in,out] ph request that failed
1037 : * @param j reply from the server (or NULL)
1038 : */
1039 : static void
1040 2 : handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
1041 : const json_t *j)
1042 : {
1043 : enum GNUNET_GenericReturnValue res;
1044 :
1045 : {
1046 : const char *msg;
1047 : const char *desc;
1048 : struct GNUNET_JSON_Specification spec[] = {
1049 2 : GNUNET_JSON_spec_string ("error",
1050 : &msg),
1051 2 : GNUNET_JSON_spec_string ("error_description",
1052 : &desc),
1053 2 : GNUNET_JSON_spec_end ()
1054 : };
1055 : const char *emsg;
1056 : unsigned int line;
1057 :
1058 2 : res = GNUNET_JSON_parse (j,
1059 : spec,
1060 : &emsg,
1061 : &line);
1062 : }
1063 :
1064 2 : if (GNUNET_OK != res)
1065 : {
1066 : json_t *body;
1067 :
1068 1 : GNUNET_break_op (0);
1069 1 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1070 : ph->http_status
1071 1 : = MHD_HTTP_BAD_GATEWAY;
1072 1 : body = GNUNET_JSON_PACK (
1073 : GNUNET_JSON_pack_allow_null (
1074 : GNUNET_JSON_pack_object_incref ("server_response",
1075 : (json_t *) j)),
1076 : GNUNET_JSON_pack_bool ("debug",
1077 : ph->pd->debug_mode),
1078 : TALER_JSON_pack_ec (
1079 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1080 1 : GNUNET_assert (NULL != body);
1081 1 : GNUNET_break (
1082 : GNUNET_SYSERR !=
1083 : templating_build (ph->connection,
1084 : &ph->http_status,
1085 : "oauth2-authorization-failure-malformed",
1086 : body,
1087 : &ph->response));
1088 1 : json_decref (body);
1089 1 : return;
1090 : }
1091 1 : ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
1092 1 : ph->http_status = MHD_HTTP_FORBIDDEN;
1093 1 : GNUNET_break (
1094 : GNUNET_SYSERR !=
1095 : templating_build (ph->connection,
1096 : &ph->http_status,
1097 : "oauth2-authorization-failure",
1098 : j,
1099 : &ph->response));
1100 : }
1101 :
1102 :
1103 : /**
1104 : * Type of a callback that receives a JSON @a result.
1105 : *
1106 : * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
1107 : * @param status_type how did the process die
1108 : * @param code termination status code from the process
1109 : * @param attr result some JSON result, NULL if we failed to get an JSON output
1110 : */
1111 : static void
1112 9 : converted_proof_cb (void *cls,
1113 : enum GNUNET_OS_ProcessStatusType status_type,
1114 : unsigned long code,
1115 : const json_t *attr)
1116 : {
1117 9 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1118 9 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
1119 :
1120 9 : ph->ec = NULL;
1121 9 : if ( (NULL == attr) ||
1122 : (0 != code) )
1123 : {
1124 : json_t *body;
1125 : char *msg;
1126 :
1127 0 : GNUNET_break_op (0);
1128 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1129 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1130 0 : if (0 != code)
1131 0 : GNUNET_asprintf (&msg,
1132 : "Attribute converter exited with status %ld",
1133 : code);
1134 : else
1135 0 : msg = GNUNET_strdup (
1136 : "Attribute converter response was not in JSON format");
1137 0 : body = GNUNET_JSON_PACK (
1138 : GNUNET_JSON_pack_string ("converter",
1139 : pd->conversion_binary),
1140 : GNUNET_JSON_pack_allow_null (
1141 : GNUNET_JSON_pack_object_incref ("attributes",
1142 : (json_t *) attr)),
1143 : GNUNET_JSON_pack_bool ("debug",
1144 : ph->pd->debug_mode),
1145 : GNUNET_JSON_pack_string ("message",
1146 : msg),
1147 : TALER_JSON_pack_ec (
1148 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1149 0 : GNUNET_free (msg);
1150 0 : GNUNET_break (
1151 : GNUNET_SYSERR !=
1152 : templating_build (ph->connection,
1153 : &ph->http_status,
1154 : "oauth2-conversion-failure",
1155 : body,
1156 : &ph->response));
1157 0 : json_decref (body);
1158 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1159 : ph);
1160 0 : return;
1161 : }
1162 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1163 : "Attribute conversion output is:\n");
1164 : #if DEBUG
1165 : json_dumpf (attr,
1166 : stderr,
1167 : JSON_INDENT (2));
1168 : fprintf (stderr,
1169 : "\n");
1170 : #endif
1171 : {
1172 : const char *id;
1173 : struct GNUNET_JSON_Specification ispec[] = {
1174 9 : GNUNET_JSON_spec_string ("id",
1175 : &id),
1176 9 : GNUNET_JSON_spec_end ()
1177 : };
1178 : enum GNUNET_GenericReturnValue res;
1179 : const char *emsg;
1180 : unsigned int line;
1181 :
1182 9 : res = GNUNET_JSON_parse (attr,
1183 : ispec,
1184 : &emsg,
1185 : &line);
1186 9 : if (GNUNET_OK != res)
1187 : {
1188 : json_t *body;
1189 :
1190 0 : GNUNET_break_op (0);
1191 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1192 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1193 0 : body = GNUNET_JSON_PACK (
1194 : GNUNET_JSON_pack_string ("converter",
1195 : pd->conversion_binary),
1196 : GNUNET_JSON_pack_string ("message",
1197 : "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
1198 : GNUNET_JSON_pack_bool ("debug",
1199 : ph->pd->debug_mode),
1200 : GNUNET_JSON_pack_object_incref ("attributes",
1201 : (json_t *) attr),
1202 : TALER_JSON_pack_ec (
1203 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1204 0 : GNUNET_break (
1205 : GNUNET_SYSERR !=
1206 : templating_build (ph->connection,
1207 : &ph->http_status,
1208 : "oauth2-conversion-failure",
1209 : body,
1210 : &ph->response));
1211 0 : json_decref (body);
1212 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1213 : ph);
1214 0 : return;
1215 : }
1216 9 : ph->provider_user_id = GNUNET_strdup (id);
1217 : }
1218 9 : if (! json_is_string (json_object_get (attr,
1219 : "FORM_ID")))
1220 : {
1221 : json_t *body;
1222 :
1223 0 : GNUNET_break_op (0);
1224 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1225 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1226 0 : body = GNUNET_JSON_PACK (
1227 : GNUNET_JSON_pack_string ("converter",
1228 : pd->conversion_binary),
1229 : GNUNET_JSON_pack_string ("message",
1230 : "Missing 'FORM_ID' field in attributes"),
1231 : GNUNET_JSON_pack_bool ("debug",
1232 : ph->pd->debug_mode),
1233 : GNUNET_JSON_pack_object_incref ("attributes",
1234 : (json_t *) attr),
1235 : TALER_JSON_pack_ec (
1236 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1237 0 : GNUNET_break (
1238 : GNUNET_SYSERR !=
1239 : templating_build (ph->connection,
1240 : &ph->http_status,
1241 : "oauth2-conversion-failure",
1242 : body,
1243 : &ph->response));
1244 0 : json_decref (body);
1245 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1246 : ph);
1247 0 : return;
1248 : }
1249 9 : ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
1250 9 : ph->response = MHD_create_response_from_buffer_static (0,
1251 : "");
1252 9 : GNUNET_assert (NULL != ph->response);
1253 9 : GNUNET_break (MHD_YES ==
1254 : MHD_add_response_header (
1255 : ph->response,
1256 : MHD_HTTP_HEADER_LOCATION,
1257 : ph->pd->post_kyc_redirect_url));
1258 9 : ph->http_status = MHD_HTTP_SEE_OTHER;
1259 9 : ph->attributes = json_incref ((json_t *) attr);
1260 9 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1261 : ph);
1262 : }
1263 :
1264 :
1265 : /**
1266 : * The request for @a ph succeeded (presumably).
1267 : * Call continuation with the result.
1268 : *
1269 : * @param[in,out] ph request that succeeded
1270 : * @param j reply from the server
1271 : */
1272 : static void
1273 9 : parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
1274 : const json_t *j)
1275 : {
1276 9 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
1277 9 : const char *argv[] = {
1278 9 : pd->conversion_binary,
1279 : NULL,
1280 : };
1281 :
1282 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1283 : "Calling converter `%s' with JSON\n",
1284 : pd->conversion_binary);
1285 : #if DEBUG
1286 : json_dumpf (j,
1287 : stderr,
1288 : JSON_INDENT (2));
1289 : #endif
1290 18 : ph->ec = TALER_JSON_external_conversion_start (
1291 : j,
1292 : &converted_proof_cb,
1293 : ph,
1294 9 : pd->conversion_binary,
1295 : argv);
1296 9 : if (NULL != ph->ec)
1297 9 : return;
1298 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1299 : "Failed to start OAUTH2 conversion helper `%s'\n",
1300 : pd->conversion_binary);
1301 0 : ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
1302 0 : ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1303 : {
1304 : json_t *body;
1305 :
1306 0 : body = GNUNET_JSON_PACK (
1307 : GNUNET_JSON_pack_string ("converter",
1308 : pd->conversion_binary),
1309 : GNUNET_JSON_pack_bool ("debug",
1310 : ph->pd->debug_mode),
1311 : GNUNET_JSON_pack_string ("message",
1312 : "Failed to launch KYC conversion helper process."),
1313 : TALER_JSON_pack_ec (
1314 : TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
1315 0 : GNUNET_break (
1316 : GNUNET_SYSERR !=
1317 : templating_build (ph->connection,
1318 : &ph->http_status,
1319 : "oauth2-conversion-failure",
1320 : body,
1321 : &ph->response));
1322 0 : json_decref (body);
1323 : }
1324 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1325 : ph);
1326 : }
1327 :
1328 :
1329 : /**
1330 : * After we are done with the CURL interaction we
1331 : * need to update our database state with the information
1332 : * retrieved.
1333 : *
1334 : * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
1335 : * @param response_code HTTP response code from server, 0 on hard error
1336 : * @param response in JSON, NULL if response was not in JSON format
1337 : */
1338 : static void
1339 9 : handle_curl_proof_finished (void *cls,
1340 : long response_code,
1341 : const void *response)
1342 : {
1343 9 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1344 9 : const json_t *j = response;
1345 :
1346 9 : ph->job = NULL;
1347 9 : switch (response_code)
1348 : {
1349 0 : case 0:
1350 : {
1351 : json_t *body;
1352 :
1353 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1354 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1355 :
1356 0 : body = GNUNET_JSON_PACK (
1357 : GNUNET_JSON_pack_string ("message",
1358 : "No response from KYC gateway"),
1359 : TALER_JSON_pack_ec (
1360 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1361 0 : GNUNET_break (
1362 : GNUNET_SYSERR !=
1363 : templating_build (ph->connection,
1364 : &ph->http_status,
1365 : "oauth2-provider-failure",
1366 : body,
1367 : &ph->response));
1368 0 : json_decref (body);
1369 : }
1370 0 : break;
1371 9 : case MHD_HTTP_OK:
1372 9 : parse_proof_success_reply (ph,
1373 : j);
1374 9 : return;
1375 0 : default:
1376 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1377 : "OAuth2.0 info URL returned HTTP status %u\n",
1378 : (unsigned int) response_code);
1379 0 : handle_proof_error (ph,
1380 : j);
1381 0 : break;
1382 : }
1383 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1384 : ph);
1385 : }
1386 :
1387 :
1388 : /**
1389 : * After we are done with the CURL interaction we
1390 : * need to fetch the user's account details.
1391 : *
1392 : * @param cls our `struct KycProofContext`
1393 : * @param response_code HTTP response code from server, 0 on hard error
1394 : * @param response in JSON, NULL if response was not in JSON format
1395 : */
1396 : static void
1397 11 : handle_curl_login_finished (void *cls,
1398 : long response_code,
1399 : const void *response)
1400 : {
1401 11 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1402 11 : const json_t *j = response;
1403 :
1404 11 : ph->job = NULL;
1405 11 : switch (response_code)
1406 : {
1407 9 : case MHD_HTTP_OK:
1408 : {
1409 : const char *access_token;
1410 : const char *token_type;
1411 : uint64_t expires_in_s;
1412 : const char *refresh_token;
1413 : bool no_expires;
1414 : bool no_refresh;
1415 : struct GNUNET_JSON_Specification spec[] = {
1416 9 : GNUNET_JSON_spec_string ("access_token",
1417 : &access_token),
1418 9 : GNUNET_JSON_spec_string ("token_type",
1419 : &token_type),
1420 9 : GNUNET_JSON_spec_mark_optional (
1421 : GNUNET_JSON_spec_uint64 ("expires_in",
1422 : &expires_in_s),
1423 : &no_expires),
1424 9 : GNUNET_JSON_spec_mark_optional (
1425 : GNUNET_JSON_spec_string ("refresh_token",
1426 : &refresh_token),
1427 : &no_refresh),
1428 9 : GNUNET_JSON_spec_end ()
1429 : };
1430 : CURL *eh;
1431 :
1432 : {
1433 : enum GNUNET_GenericReturnValue res;
1434 : const char *emsg;
1435 : unsigned int line;
1436 :
1437 9 : res = GNUNET_JSON_parse (j,
1438 : spec,
1439 : &emsg,
1440 : &line);
1441 9 : if (GNUNET_OK != res)
1442 : {
1443 : json_t *body;
1444 :
1445 0 : GNUNET_break_op (0);
1446 : ph->http_status
1447 0 : = MHD_HTTP_BAD_GATEWAY;
1448 0 : body = GNUNET_JSON_PACK (
1449 : GNUNET_JSON_pack_object_incref ("server_response",
1450 : (json_t *) j),
1451 : GNUNET_JSON_pack_bool ("debug",
1452 : ph->pd->debug_mode),
1453 : GNUNET_JSON_pack_string ("message",
1454 : "Unexpected response from KYC gateway: required fields missing or malformed"),
1455 : TALER_JSON_pack_ec (
1456 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1457 0 : GNUNET_break (
1458 : GNUNET_SYSERR !=
1459 : templating_build (ph->connection,
1460 : &ph->http_status,
1461 : "oauth2-provider-failure",
1462 : body,
1463 : &ph->response));
1464 0 : json_decref (body);
1465 0 : break;
1466 : }
1467 : }
1468 9 : if (0 != strcasecmp (token_type,
1469 : "bearer"))
1470 : {
1471 : json_t *body;
1472 :
1473 0 : GNUNET_break_op (0);
1474 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1475 0 : body = GNUNET_JSON_PACK (
1476 : GNUNET_JSON_pack_object_incref ("server_response",
1477 : (json_t *) j),
1478 : GNUNET_JSON_pack_bool ("debug",
1479 : ph->pd->debug_mode),
1480 : GNUNET_JSON_pack_string ("message",
1481 : "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
1482 : TALER_JSON_pack_ec (
1483 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1484 0 : GNUNET_break (
1485 : GNUNET_SYSERR !=
1486 : templating_build (ph->connection,
1487 : &ph->http_status,
1488 : "oauth2-provider-failure",
1489 : body,
1490 : &ph->response));
1491 0 : json_decref (body);
1492 0 : break;
1493 : }
1494 :
1495 : /* We guard against a few characters that could
1496 : conceivably be abused to mess with the HTTP header */
1497 9 : if ( (NULL != strchr (access_token,
1498 9 : '\n')) ||
1499 9 : (NULL != strchr (access_token,
1500 9 : '\r')) ||
1501 9 : (NULL != strchr (access_token,
1502 9 : ' ')) ||
1503 9 : (NULL != strchr (access_token,
1504 : ';')) )
1505 : {
1506 : json_t *body;
1507 :
1508 0 : GNUNET_break_op (0);
1509 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1510 0 : body = GNUNET_JSON_PACK (
1511 : GNUNET_JSON_pack_object_incref ("server_response",
1512 : (json_t *) j),
1513 : GNUNET_JSON_pack_bool ("debug",
1514 : ph->pd->debug_mode),
1515 : GNUNET_JSON_pack_string ("message",
1516 : "Illegal character in access token"),
1517 : TALER_JSON_pack_ec (
1518 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1519 0 : GNUNET_break (
1520 : GNUNET_SYSERR !=
1521 : templating_build (ph->connection,
1522 : &ph->http_status,
1523 : "oauth2-provider-failure",
1524 : body,
1525 : &ph->response));
1526 0 : json_decref (body);
1527 0 : break;
1528 : }
1529 :
1530 9 : eh = curl_easy_init ();
1531 9 : GNUNET_assert (NULL != eh);
1532 9 : GNUNET_assert (CURLE_OK ==
1533 : curl_easy_setopt (eh,
1534 : CURLOPT_URL,
1535 : ph->pd->info_url));
1536 : {
1537 : char *hdr;
1538 : struct curl_slist *slist;
1539 :
1540 9 : GNUNET_asprintf (&hdr,
1541 : "%s: Bearer %s",
1542 : MHD_HTTP_HEADER_AUTHORIZATION,
1543 : access_token);
1544 9 : slist = curl_slist_append (NULL,
1545 : hdr);
1546 9 : ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
1547 : eh,
1548 : slist,
1549 : &handle_curl_proof_finished,
1550 : ph);
1551 9 : curl_slist_free_all (slist);
1552 9 : GNUNET_free (hdr);
1553 : }
1554 9 : return;
1555 : }
1556 2 : default:
1557 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1558 : "OAuth2.0 login URL returned HTTP status %u\n",
1559 : (unsigned int) response_code);
1560 2 : handle_proof_error (ph,
1561 : j);
1562 2 : break;
1563 : }
1564 2 : return_proof_response (ph);
1565 : }
1566 :
1567 :
1568 : /**
1569 : * Check KYC status and return status to human.
1570 : *
1571 : * @param cls the @e cls of this struct with the plugin-specific state
1572 : * @param pd provider configuration details
1573 : * @param connection MHD connection object (for HTTP headers)
1574 : * @param account_id which account to trigger process for
1575 : * @param process_row row in the legitimization processes table the legitimization is for
1576 : * @param provider_user_id user ID (or NULL) the proof is for
1577 : * @param provider_legitimization_id legitimization ID the proof is for
1578 : * @param cb function to call with the result
1579 : * @param cb_cls closure for @a cb
1580 : * @return handle to cancel operation early
1581 : */
1582 : static struct TALER_KYCLOGIC_ProofHandle *
1583 11 : oauth2_proof (void *cls,
1584 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1585 : struct MHD_Connection *connection,
1586 : const struct TALER_NormalizedPaytoHashP *account_id,
1587 : uint64_t process_row,
1588 : const char *provider_user_id,
1589 : const char *provider_legitimization_id,
1590 : TALER_KYCLOGIC_ProofCallback cb,
1591 : void *cb_cls)
1592 : {
1593 11 : struct PluginState *ps = cls;
1594 : struct TALER_KYCLOGIC_ProofHandle *ph;
1595 : const char *code;
1596 :
1597 11 : GNUNET_break (NULL == provider_user_id);
1598 11 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
1599 11 : GNUNET_snprintf (ph->provider_legitimization_id,
1600 : sizeof (ph->provider_legitimization_id),
1601 : "%llu",
1602 : (unsigned long long) process_row);
1603 11 : if ( (NULL != provider_legitimization_id) &&
1604 11 : (0 != strcmp (provider_legitimization_id,
1605 11 : ph->provider_legitimization_id)))
1606 : {
1607 0 : GNUNET_break (0);
1608 0 : GNUNET_free (ph);
1609 0 : return NULL;
1610 : }
1611 :
1612 11 : ph->pd = pd;
1613 11 : ph->connection = connection;
1614 11 : ph->h_payto = *account_id;
1615 11 : ph->cb = cb;
1616 11 : ph->cb_cls = cb_cls;
1617 11 : code = MHD_lookup_connection_value (connection,
1618 : MHD_GET_ARGUMENT_KIND,
1619 : "code");
1620 11 : if (NULL == code)
1621 : {
1622 : const char *err;
1623 : const char *desc;
1624 : const char *euri;
1625 : json_t *body;
1626 :
1627 0 : err = MHD_lookup_connection_value (connection,
1628 : MHD_GET_ARGUMENT_KIND,
1629 : "error");
1630 0 : if (NULL == err)
1631 : {
1632 0 : GNUNET_break_op (0);
1633 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
1634 0 : ph->http_status = MHD_HTTP_BAD_REQUEST;
1635 0 : body = GNUNET_JSON_PACK (
1636 : GNUNET_JSON_pack_string ("message",
1637 : "'code' parameter malformed"),
1638 : TALER_JSON_pack_ec (
1639 : TALER_EC_GENERIC_PARAMETER_MALFORMED));
1640 0 : GNUNET_break (
1641 : GNUNET_SYSERR !=
1642 : templating_build (ph->connection,
1643 : &ph->http_status,
1644 : "oauth2-bad-request",
1645 : body,
1646 : &ph->response));
1647 0 : json_decref (body);
1648 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1649 : ph);
1650 0 : return ph;
1651 : }
1652 0 : desc = MHD_lookup_connection_value (connection,
1653 : MHD_GET_ARGUMENT_KIND,
1654 : "error_description");
1655 0 : euri = MHD_lookup_connection_value (connection,
1656 : MHD_GET_ARGUMENT_KIND,
1657 : "error_uri");
1658 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1659 : "OAuth2 process %llu failed with error `%s'\n",
1660 : (unsigned long long) process_row,
1661 : err);
1662 0 : if (0 == strcasecmp (err,
1663 : "server_error"))
1664 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1665 0 : else if (0 == strcasecmp (err,
1666 : "unauthorized_client"))
1667 0 : ph->status = TALER_KYCLOGIC_STATUS_FAILED;
1668 0 : else if (0 == strcasecmp (err,
1669 : "temporarily_unavailable"))
1670 0 : ph->status = TALER_KYCLOGIC_STATUS_PENDING;
1671 : else
1672 0 : ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
1673 0 : ph->http_status = MHD_HTTP_FORBIDDEN;
1674 0 : body = GNUNET_JSON_PACK (
1675 : GNUNET_JSON_pack_string ("error",
1676 : err),
1677 : GNUNET_JSON_pack_allow_null (
1678 : GNUNET_JSON_pack_string ("error_details",
1679 : desc)),
1680 : GNUNET_JSON_pack_allow_null (
1681 : GNUNET_JSON_pack_string ("error_uri",
1682 : euri)));
1683 0 : GNUNET_break (
1684 : GNUNET_SYSERR !=
1685 : templating_build (ph->connection,
1686 : &ph->http_status,
1687 : "oauth2-authentication-failure",
1688 : body,
1689 : &ph->response));
1690 0 : json_decref (body);
1691 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1692 : ph);
1693 0 : return ph;
1694 :
1695 : }
1696 :
1697 11 : ph->eh = curl_easy_init ();
1698 11 : GNUNET_assert (NULL != ph->eh);
1699 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1700 : "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
1701 : pd->token_url);
1702 11 : GNUNET_assert (CURLE_OK ==
1703 : curl_easy_setopt (ph->eh,
1704 : CURLOPT_URL,
1705 : pd->token_url));
1706 11 : GNUNET_assert (CURLE_OK ==
1707 : curl_easy_setopt (ph->eh,
1708 : CURLOPT_VERBOSE,
1709 : 1));
1710 11 : GNUNET_assert (CURLE_OK ==
1711 : curl_easy_setopt (ph->eh,
1712 : CURLOPT_POST,
1713 : 1));
1714 : {
1715 : char *client_id;
1716 : char *client_secret;
1717 : char *authorization_code;
1718 : char *redirect_uri_encoded;
1719 : char *hps;
1720 :
1721 11 : hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
1722 : sizeof (ph->h_payto));
1723 : {
1724 : char *redirect_uri;
1725 :
1726 11 : GNUNET_asprintf (&redirect_uri,
1727 : "%skyc-proof/%s",
1728 : ps->exchange_base_url,
1729 11 : &pd->section[strlen ("kyc-provider-")]);
1730 11 : redirect_uri_encoded = TALER_urlencode (redirect_uri);
1731 11 : GNUNET_free (redirect_uri);
1732 : }
1733 11 : GNUNET_assert (NULL != redirect_uri_encoded);
1734 11 : client_id = curl_easy_escape (ph->eh,
1735 11 : pd->client_id,
1736 : 0);
1737 11 : GNUNET_assert (NULL != client_id);
1738 11 : client_secret = curl_easy_escape (ph->eh,
1739 11 : pd->client_secret,
1740 : 0);
1741 11 : GNUNET_assert (NULL != client_secret);
1742 11 : authorization_code = curl_easy_escape (ph->eh,
1743 : code,
1744 : 0);
1745 11 : GNUNET_assert (NULL != authorization_code);
1746 11 : GNUNET_asprintf (&ph->post_body,
1747 : "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
1748 : client_id,
1749 : redirect_uri_encoded,
1750 : hps,
1751 : client_secret,
1752 : authorization_code);
1753 11 : curl_free (authorization_code);
1754 11 : curl_free (client_secret);
1755 11 : GNUNET_free (redirect_uri_encoded);
1756 11 : GNUNET_free (hps);
1757 11 : curl_free (client_id);
1758 : }
1759 11 : GNUNET_assert (CURLE_OK ==
1760 : curl_easy_setopt (ph->eh,
1761 : CURLOPT_POSTFIELDS,
1762 : ph->post_body));
1763 11 : GNUNET_assert (CURLE_OK ==
1764 : curl_easy_setopt (ph->eh,
1765 : CURLOPT_FOLLOWLOCATION,
1766 : 1L));
1767 : /* limit MAXREDIRS to 5 as a simple security measure against
1768 : a potential infinite loop caused by a malicious target */
1769 11 : GNUNET_assert (CURLE_OK ==
1770 : curl_easy_setopt (ph->eh,
1771 : CURLOPT_MAXREDIRS,
1772 : 5L));
1773 :
1774 11 : ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
1775 : ph->eh,
1776 : &handle_curl_login_finished,
1777 : ph);
1778 11 : return ph;
1779 : }
1780 :
1781 :
1782 : /**
1783 : * Function to asynchronously return the 404 not found
1784 : * page for the webhook.
1785 : *
1786 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
1787 : */
1788 : static void
1789 0 : wh_return_not_found (void *cls)
1790 : {
1791 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1792 : struct MHD_Response *response;
1793 :
1794 0 : wh->task = NULL;
1795 0 : response = MHD_create_response_from_buffer_static (0,
1796 : "");
1797 0 : wh->cb (wh->cb_cls,
1798 : 0LLU,
1799 : NULL,
1800 : false,
1801 : NULL,
1802 : NULL,
1803 : NULL,
1804 : TALER_KYCLOGIC_STATUS_KEEP,
1805 0 : GNUNET_TIME_UNIT_ZERO_ABS,
1806 : NULL,
1807 : MHD_HTTP_NOT_FOUND,
1808 : response);
1809 0 : GNUNET_free (wh);
1810 0 : }
1811 :
1812 :
1813 : /**
1814 : * Check KYC status and return result for Webhook.
1815 : *
1816 : * @param cls the @e cls of this struct with the plugin-specific state
1817 : * @param pd provider configuration details
1818 : * @param plc callback to lookup accounts with
1819 : * @param plc_cls closure for @a plc
1820 : * @param http_method HTTP method used for the webhook
1821 : * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
1822 : * @param connection MHD connection object (for HTTP headers)
1823 : * @param body HTTP request body, or NULL if not available
1824 : * @param cb function to call with the result
1825 : * @param cb_cls closure for @a cb
1826 : * @return handle to cancel operation early
1827 : */
1828 : static struct TALER_KYCLOGIC_WebhookHandle *
1829 0 : oauth2_webhook (void *cls,
1830 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1831 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1832 : void *plc_cls,
1833 : const char *http_method,
1834 : const char *const url_path[],
1835 : struct MHD_Connection *connection,
1836 : const json_t *body,
1837 : TALER_KYCLOGIC_WebhookCallback cb,
1838 : void *cb_cls)
1839 : {
1840 0 : struct PluginState *ps = cls;
1841 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1842 :
1843 : (void) pd;
1844 : (void) plc;
1845 : (void) plc_cls;
1846 : (void) http_method;
1847 : (void) url_path;
1848 : (void) connection;
1849 : (void) body;
1850 0 : GNUNET_break_op (0);
1851 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1852 0 : wh->cb = cb;
1853 0 : wh->cb_cls = cb_cls;
1854 0 : wh->ps = ps;
1855 0 : wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
1856 : wh);
1857 0 : return wh;
1858 : }
1859 :
1860 :
1861 : /**
1862 : * Cancel KYC webhook execution.
1863 : *
1864 : * @param[in] wh handle of operation to cancel
1865 : */
1866 : static void
1867 0 : oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
1868 : {
1869 0 : GNUNET_SCHEDULER_cancel (wh->task);
1870 0 : GNUNET_free (wh);
1871 0 : }
1872 :
1873 :
1874 : /**
1875 : * Initialize OAuth2.0 KYC logic plugin
1876 : *
1877 : * @param cls a configuration instance
1878 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
1879 : */
1880 : void *
1881 : libtaler_plugin_kyclogic_oauth2_init (void *cls);
1882 :
1883 : /* declaration to avoid compiler warning */
1884 : void *
1885 61 : libtaler_plugin_kyclogic_oauth2_init (void *cls)
1886 : {
1887 61 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
1888 : struct TALER_KYCLOGIC_Plugin *plugin;
1889 : struct PluginState *ps;
1890 :
1891 61 : ps = GNUNET_new (struct PluginState);
1892 61 : ps->cfg = cfg;
1893 61 : if (GNUNET_OK !=
1894 61 : GNUNET_CONFIGURATION_get_value_string (cfg,
1895 : "exchange",
1896 : "BASE_URL",
1897 : &ps->exchange_base_url))
1898 : {
1899 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1900 : "exchange",
1901 : "BASE_URL");
1902 0 : GNUNET_free (ps);
1903 0 : return NULL;
1904 : }
1905 : ps->curl_ctx
1906 122 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1907 61 : &ps->curl_rc);
1908 61 : if (NULL == ps->curl_ctx)
1909 : {
1910 0 : GNUNET_break (0);
1911 0 : GNUNET_free (ps->exchange_base_url);
1912 0 : GNUNET_free (ps);
1913 0 : return NULL;
1914 : }
1915 61 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
1916 :
1917 61 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
1918 61 : plugin->cls = ps;
1919 : plugin->load_configuration
1920 61 : = &oauth2_load_configuration;
1921 : plugin->unload_configuration
1922 61 : = &oauth2_unload_configuration;
1923 : plugin->initiate
1924 61 : = &oauth2_initiate;
1925 : plugin->initiate_cancel
1926 61 : = &oauth2_initiate_cancel;
1927 : plugin->proof
1928 61 : = &oauth2_proof;
1929 : plugin->proof_cancel
1930 61 : = &oauth2_proof_cancel;
1931 : plugin->webhook
1932 61 : = &oauth2_webhook;
1933 : plugin->webhook_cancel
1934 61 : = &oauth2_webhook_cancel;
1935 61 : return plugin;
1936 : }
1937 :
1938 :
1939 : /**
1940 : * Unload authorization plugin
1941 : *
1942 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
1943 : * @return NULL (always)
1944 : */
1945 : void *
1946 : libtaler_plugin_kyclogic_oauth2_done (void *cls);
1947 :
1948 : /* declaration to avoid compiler warning */
1949 : void *
1950 61 : libtaler_plugin_kyclogic_oauth2_done (void *cls)
1951 : {
1952 61 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
1953 61 : struct PluginState *ps = plugin->cls;
1954 :
1955 61 : if (NULL != ps->curl_ctx)
1956 : {
1957 61 : GNUNET_CURL_fini (ps->curl_ctx);
1958 61 : ps->curl_ctx = NULL;
1959 : }
1960 61 : if (NULL != ps->curl_rc)
1961 : {
1962 61 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
1963 61 : ps->curl_rc = NULL;
1964 : }
1965 61 : GNUNET_free (ps->exchange_base_url);
1966 61 : GNUNET_free (ps);
1967 61 : GNUNET_free (plugin);
1968 61 : return NULL;
1969 : }
|