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