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