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 : * Load a @a template and substitute using @a root, returning the result in a
992 : * @a reply encoded suitable for the @a connection with the given @a
993 : * http_status code. On errors, the @a http_status code
994 : * is updated to reflect the type of error encoded in the
995 : * @a reply.
996 : *
997 : * @param connection the connection we act upon
998 : * @param[in,out] http_status code to use on success,
999 : * set to alternative code on failure
1000 : * @param template basename of the template to load
1001 : * @param root JSON object to pass as the root context
1002 : * @param[out] reply where to write the response object
1003 : * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
1004 : * #GNUNET_SYSERR on failure (to queue an error)
1005 : */
1006 : static enum GNUNET_GenericReturnValue
1007 2 : templating_build (struct MHD_Connection *connection,
1008 : unsigned int *http_status,
1009 : const char *template,
1010 : const json_t *root,
1011 : struct MHD_Response **reply)
1012 : {
1013 : enum GNUNET_GenericReturnValue ret;
1014 :
1015 2 : ret = TALER_TEMPLATING_build (connection,
1016 : http_status,
1017 : template,
1018 : NULL,
1019 : NULL,
1020 : root,
1021 : reply);
1022 2 : if (GNUNET_SYSERR != ret)
1023 : {
1024 2 : GNUNET_break (MHD_NO !=
1025 : MHD_add_response_header (*reply,
1026 : MHD_HTTP_HEADER_CONTENT_TYPE,
1027 : "text/html"));
1028 : }
1029 2 : return ret;
1030 : }
1031 :
1032 :
1033 : /**
1034 : * The request for @a ph failed. We may have gotten a useful error
1035 : * message in @a j. Generate a failure response.
1036 : *
1037 : * @param[in,out] ph request that failed
1038 : * @param j reply from the server (or NULL)
1039 : */
1040 : static void
1041 2 : handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
1042 : const json_t *j)
1043 : {
1044 : enum GNUNET_GenericReturnValue res;
1045 :
1046 : {
1047 : const char *msg;
1048 : const char *desc;
1049 : struct GNUNET_JSON_Specification spec[] = {
1050 2 : GNUNET_JSON_spec_string ("error",
1051 : &msg),
1052 2 : GNUNET_JSON_spec_string ("error_description",
1053 : &desc),
1054 2 : GNUNET_JSON_spec_end ()
1055 : };
1056 : const char *emsg;
1057 : unsigned int line;
1058 :
1059 2 : res = GNUNET_JSON_parse (j,
1060 : spec,
1061 : &emsg,
1062 : &line);
1063 : }
1064 :
1065 2 : if (GNUNET_OK != res)
1066 : {
1067 : json_t *body;
1068 :
1069 1 : GNUNET_break_op (0);
1070 1 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1071 : ph->http_status
1072 1 : = MHD_HTTP_BAD_GATEWAY;
1073 1 : body = GNUNET_JSON_PACK (
1074 : GNUNET_JSON_pack_allow_null (
1075 : GNUNET_JSON_pack_object_incref ("server_response",
1076 : (json_t *) j)),
1077 : GNUNET_JSON_pack_bool ("debug",
1078 : ph->pd->debug_mode),
1079 : TALER_JSON_pack_ec (
1080 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1081 1 : GNUNET_assert (NULL != body);
1082 1 : GNUNET_break (
1083 : GNUNET_SYSERR !=
1084 : templating_build (ph->connection,
1085 : &ph->http_status,
1086 : "oauth2-authorization-failure-malformed",
1087 : body,
1088 : &ph->response));
1089 1 : json_decref (body);
1090 1 : return;
1091 : }
1092 1 : ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
1093 1 : ph->http_status = MHD_HTTP_FORBIDDEN;
1094 1 : GNUNET_break (
1095 : GNUNET_SYSERR !=
1096 : templating_build (ph->connection,
1097 : &ph->http_status,
1098 : "oauth2-authorization-failure",
1099 : j,
1100 : &ph->response));
1101 : }
1102 :
1103 :
1104 : /**
1105 : * Type of a callback that receives a JSON @a result.
1106 : *
1107 : * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
1108 : * @param status_type how did the process die
1109 : * @param code termination status code from the process
1110 : * @param attr result some JSON result, NULL if we failed to get an JSON output
1111 : */
1112 : static void
1113 9 : converted_proof_cb (void *cls,
1114 : enum GNUNET_OS_ProcessStatusType status_type,
1115 : unsigned long code,
1116 : const json_t *attr)
1117 : {
1118 9 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1119 9 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
1120 :
1121 9 : ph->ec = NULL;
1122 9 : if ( (NULL == attr) ||
1123 : (0 != code) )
1124 : {
1125 : json_t *body;
1126 : char *msg;
1127 :
1128 0 : GNUNET_break_op (0);
1129 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1130 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1131 0 : if (0 != code)
1132 0 : GNUNET_asprintf (&msg,
1133 : "Attribute converter exited with status %ld",
1134 : code);
1135 : else
1136 0 : msg = GNUNET_strdup (
1137 : "Attribute converter response was not in JSON format");
1138 0 : body = GNUNET_JSON_PACK (
1139 : GNUNET_JSON_pack_string ("converter",
1140 : pd->conversion_binary),
1141 : GNUNET_JSON_pack_allow_null (
1142 : GNUNET_JSON_pack_object_incref ("attributes",
1143 : (json_t *) attr)),
1144 : GNUNET_JSON_pack_bool ("debug",
1145 : ph->pd->debug_mode),
1146 : GNUNET_JSON_pack_string ("message",
1147 : msg),
1148 : TALER_JSON_pack_ec (
1149 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1150 0 : GNUNET_free (msg);
1151 0 : GNUNET_break (
1152 : GNUNET_SYSERR !=
1153 : templating_build (ph->connection,
1154 : &ph->http_status,
1155 : "oauth2-conversion-failure",
1156 : body,
1157 : &ph->response));
1158 0 : json_decref (body);
1159 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1160 : ph);
1161 0 : return;
1162 : }
1163 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1164 : "Attribute conversion output is:\n");
1165 : #if DEBUG
1166 : json_dumpf (attr,
1167 : stderr,
1168 : JSON_INDENT (2));
1169 : fprintf (stderr,
1170 : "\n");
1171 : #endif
1172 : {
1173 : const char *id;
1174 : struct GNUNET_JSON_Specification ispec[] = {
1175 9 : GNUNET_JSON_spec_string ("id",
1176 : &id),
1177 9 : GNUNET_JSON_spec_end ()
1178 : };
1179 : enum GNUNET_GenericReturnValue res;
1180 : const char *emsg;
1181 : unsigned int line;
1182 :
1183 9 : res = GNUNET_JSON_parse (attr,
1184 : ispec,
1185 : &emsg,
1186 : &line);
1187 9 : if (GNUNET_OK != res)
1188 : {
1189 : json_t *body;
1190 :
1191 0 : GNUNET_break_op (0);
1192 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1193 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1194 0 : body = GNUNET_JSON_PACK (
1195 : GNUNET_JSON_pack_string ("converter",
1196 : pd->conversion_binary),
1197 : GNUNET_JSON_pack_string ("message",
1198 : "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
1199 : GNUNET_JSON_pack_bool ("debug",
1200 : ph->pd->debug_mode),
1201 : GNUNET_JSON_pack_object_incref ("attributes",
1202 : (json_t *) attr),
1203 : TALER_JSON_pack_ec (
1204 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1205 0 : GNUNET_break (
1206 : GNUNET_SYSERR !=
1207 : templating_build (ph->connection,
1208 : &ph->http_status,
1209 : "oauth2-conversion-failure",
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->provider_user_id = GNUNET_strdup (id);
1218 : }
1219 9 : if (! json_is_string (json_object_get (attr,
1220 : "FORM_ID")))
1221 : {
1222 : json_t *body;
1223 :
1224 0 : GNUNET_break_op (0);
1225 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1226 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1227 0 : body = GNUNET_JSON_PACK (
1228 : GNUNET_JSON_pack_string ("converter",
1229 : pd->conversion_binary),
1230 : GNUNET_JSON_pack_string ("message",
1231 : "Missing 'FORM_ID' field in attributes"),
1232 : GNUNET_JSON_pack_bool ("debug",
1233 : ph->pd->debug_mode),
1234 : GNUNET_JSON_pack_object_incref ("attributes",
1235 : (json_t *) attr),
1236 : TALER_JSON_pack_ec (
1237 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1238 0 : GNUNET_break (
1239 : GNUNET_SYSERR !=
1240 : templating_build (ph->connection,
1241 : &ph->http_status,
1242 : "oauth2-conversion-failure",
1243 : body,
1244 : &ph->response));
1245 0 : json_decref (body);
1246 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1247 : ph);
1248 0 : return;
1249 : }
1250 9 : ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
1251 9 : ph->response = MHD_create_response_from_buffer_static (0,
1252 : "");
1253 9 : GNUNET_assert (NULL != ph->response);
1254 9 : GNUNET_break (MHD_YES ==
1255 : MHD_add_response_header (
1256 : ph->response,
1257 : MHD_HTTP_HEADER_LOCATION,
1258 : ph->pd->post_kyc_redirect_url));
1259 9 : ph->http_status = MHD_HTTP_SEE_OTHER;
1260 9 : ph->attributes = json_incref ((json_t *) attr);
1261 9 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1262 : ph);
1263 : }
1264 :
1265 :
1266 : /**
1267 : * The request for @a ph succeeded (presumably).
1268 : * Call continuation with the result.
1269 : *
1270 : * @param[in,out] ph request that succeeded
1271 : * @param j reply from the server
1272 : */
1273 : static void
1274 9 : parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
1275 : const json_t *j)
1276 : {
1277 9 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
1278 9 : const char *argv[] = {
1279 9 : pd->conversion_binary,
1280 : NULL,
1281 : };
1282 :
1283 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1284 : "Calling converter `%s' with JSON\n",
1285 : pd->conversion_binary);
1286 : #if DEBUG
1287 : json_dumpf (j,
1288 : stderr,
1289 : JSON_INDENT (2));
1290 : #endif
1291 18 : ph->ec = TALER_JSON_external_conversion_start (
1292 : j,
1293 : &converted_proof_cb,
1294 : ph,
1295 9 : pd->conversion_binary,
1296 : argv);
1297 9 : if (NULL != ph->ec)
1298 9 : return;
1299 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1300 : "Failed to start OAUTH2 conversion helper `%s'\n",
1301 : pd->conversion_binary);
1302 0 : ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
1303 0 : ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1304 : {
1305 : json_t *body;
1306 :
1307 0 : body = GNUNET_JSON_PACK (
1308 : GNUNET_JSON_pack_string ("converter",
1309 : pd->conversion_binary),
1310 : GNUNET_JSON_pack_bool ("debug",
1311 : ph->pd->debug_mode),
1312 : GNUNET_JSON_pack_string ("message",
1313 : "Failed to launch KYC conversion helper process."),
1314 : TALER_JSON_pack_ec (
1315 : TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
1316 0 : GNUNET_break (
1317 : GNUNET_SYSERR !=
1318 : templating_build (ph->connection,
1319 : &ph->http_status,
1320 : "oauth2-conversion-failure",
1321 : body,
1322 : &ph->response));
1323 0 : json_decref (body);
1324 : }
1325 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1326 : ph);
1327 : }
1328 :
1329 :
1330 : /**
1331 : * After we are done with the CURL interaction we
1332 : * need to update our database state with the information
1333 : * retrieved.
1334 : *
1335 : * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
1336 : * @param response_code HTTP response code from server, 0 on hard error
1337 : * @param response in JSON, NULL if response was not in JSON format
1338 : */
1339 : static void
1340 9 : handle_curl_proof_finished (void *cls,
1341 : long response_code,
1342 : const void *response)
1343 : {
1344 9 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1345 9 : const json_t *j = response;
1346 :
1347 9 : ph->job = NULL;
1348 9 : switch (response_code)
1349 : {
1350 0 : case 0:
1351 : {
1352 : json_t *body;
1353 :
1354 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1355 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1356 :
1357 0 : body = GNUNET_JSON_PACK (
1358 : GNUNET_JSON_pack_string ("message",
1359 : "No response from KYC gateway"),
1360 : TALER_JSON_pack_ec (
1361 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1362 0 : GNUNET_break (
1363 : GNUNET_SYSERR !=
1364 : templating_build (ph->connection,
1365 : &ph->http_status,
1366 : "oauth2-provider-failure",
1367 : body,
1368 : &ph->response));
1369 0 : json_decref (body);
1370 : }
1371 0 : break;
1372 9 : case MHD_HTTP_OK:
1373 9 : parse_proof_success_reply (ph,
1374 : j);
1375 9 : return;
1376 0 : default:
1377 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1378 : "OAuth2.0 info URL returned HTTP status %u\n",
1379 : (unsigned int) response_code);
1380 0 : handle_proof_error (ph,
1381 : j);
1382 0 : break;
1383 : }
1384 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1385 : ph);
1386 : }
1387 :
1388 :
1389 : /**
1390 : * After we are done with the CURL interaction we
1391 : * need to fetch the user's account details.
1392 : *
1393 : * @param cls our `struct KycProofContext`
1394 : * @param response_code HTTP response code from server, 0 on hard error
1395 : * @param response in JSON, NULL if response was not in JSON format
1396 : */
1397 : static void
1398 11 : handle_curl_login_finished (void *cls,
1399 : long response_code,
1400 : const void *response)
1401 : {
1402 11 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
1403 11 : const json_t *j = response;
1404 :
1405 11 : ph->job = NULL;
1406 11 : switch (response_code)
1407 : {
1408 9 : case MHD_HTTP_OK:
1409 : {
1410 : const char *access_token;
1411 : const char *token_type;
1412 : uint64_t expires_in_s;
1413 : const char *refresh_token;
1414 : bool no_expires;
1415 : bool no_refresh;
1416 : struct GNUNET_JSON_Specification spec[] = {
1417 9 : GNUNET_JSON_spec_string ("access_token",
1418 : &access_token),
1419 9 : GNUNET_JSON_spec_string ("token_type",
1420 : &token_type),
1421 9 : GNUNET_JSON_spec_mark_optional (
1422 : GNUNET_JSON_spec_uint64 ("expires_in",
1423 : &expires_in_s),
1424 : &no_expires),
1425 9 : GNUNET_JSON_spec_mark_optional (
1426 : GNUNET_JSON_spec_string ("refresh_token",
1427 : &refresh_token),
1428 : &no_refresh),
1429 9 : GNUNET_JSON_spec_end ()
1430 : };
1431 : CURL *eh;
1432 :
1433 : {
1434 : enum GNUNET_GenericReturnValue res;
1435 : const char *emsg;
1436 : unsigned int line;
1437 :
1438 9 : res = GNUNET_JSON_parse (j,
1439 : spec,
1440 : &emsg,
1441 : &line);
1442 9 : if (GNUNET_OK != res)
1443 : {
1444 : json_t *body;
1445 :
1446 0 : GNUNET_break_op (0);
1447 : ph->http_status
1448 0 : = 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 response from KYC gateway: required fields missing or malformed"),
1456 : TALER_JSON_pack_ec (
1457 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1458 0 : GNUNET_break (
1459 : GNUNET_SYSERR !=
1460 : templating_build (ph->connection,
1461 : &ph->http_status,
1462 : "oauth2-provider-failure",
1463 : body,
1464 : &ph->response));
1465 0 : json_decref (body);
1466 0 : break;
1467 : }
1468 : }
1469 9 : if (0 != strcasecmp (token_type,
1470 : "bearer"))
1471 : {
1472 : json_t *body;
1473 :
1474 0 : GNUNET_break_op (0);
1475 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1476 0 : body = GNUNET_JSON_PACK (
1477 : GNUNET_JSON_pack_object_incref ("server_response",
1478 : (json_t *) j),
1479 : GNUNET_JSON_pack_bool ("debug",
1480 : ph->pd->debug_mode),
1481 : GNUNET_JSON_pack_string ("message",
1482 : "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
1483 : TALER_JSON_pack_ec (
1484 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1485 0 : GNUNET_break (
1486 : GNUNET_SYSERR !=
1487 : templating_build (ph->connection,
1488 : &ph->http_status,
1489 : "oauth2-provider-failure",
1490 : body,
1491 : &ph->response));
1492 0 : json_decref (body);
1493 0 : break;
1494 : }
1495 :
1496 : /* We guard against a few characters that could
1497 : conceivably be abused to mess with the HTTP header */
1498 9 : if ( (NULL != strchr (access_token,
1499 9 : '\n')) ||
1500 9 : (NULL != strchr (access_token,
1501 9 : '\r')) ||
1502 9 : (NULL != strchr (access_token,
1503 9 : ' ')) ||
1504 9 : (NULL != strchr (access_token,
1505 : ';')) )
1506 : {
1507 : json_t *body;
1508 :
1509 0 : GNUNET_break_op (0);
1510 0 : ph->http_status = MHD_HTTP_BAD_GATEWAY;
1511 0 : body = GNUNET_JSON_PACK (
1512 : GNUNET_JSON_pack_object_incref ("server_response",
1513 : (json_t *) j),
1514 : GNUNET_JSON_pack_bool ("debug",
1515 : ph->pd->debug_mode),
1516 : GNUNET_JSON_pack_string ("message",
1517 : "Illegal character in access token"),
1518 : TALER_JSON_pack_ec (
1519 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
1520 0 : GNUNET_break (
1521 : GNUNET_SYSERR !=
1522 : templating_build (ph->connection,
1523 : &ph->http_status,
1524 : "oauth2-provider-failure",
1525 : body,
1526 : &ph->response));
1527 0 : json_decref (body);
1528 0 : break;
1529 : }
1530 :
1531 9 : eh = curl_easy_init ();
1532 9 : GNUNET_assert (NULL != eh);
1533 9 : GNUNET_assert (CURLE_OK ==
1534 : curl_easy_setopt (eh,
1535 : CURLOPT_URL,
1536 : ph->pd->info_url));
1537 : {
1538 : char *hdr;
1539 : struct curl_slist *slist;
1540 :
1541 9 : GNUNET_asprintf (&hdr,
1542 : "%s: Bearer %s",
1543 : MHD_HTTP_HEADER_AUTHORIZATION,
1544 : access_token);
1545 9 : slist = curl_slist_append (NULL,
1546 : hdr);
1547 9 : ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
1548 : eh,
1549 : slist,
1550 : &handle_curl_proof_finished,
1551 : ph);
1552 9 : curl_slist_free_all (slist);
1553 9 : GNUNET_free (hdr);
1554 : }
1555 9 : return;
1556 : }
1557 2 : default:
1558 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1559 : "OAuth2.0 login URL returned HTTP status %u\n",
1560 : (unsigned int) response_code);
1561 2 : handle_proof_error (ph,
1562 : j);
1563 2 : break;
1564 : }
1565 2 : return_proof_response (ph);
1566 : }
1567 :
1568 :
1569 : /**
1570 : * Check KYC status and return status to human.
1571 : *
1572 : * @param cls the @e cls of this struct with the plugin-specific state
1573 : * @param pd provider configuration details
1574 : * @param connection MHD connection object (for HTTP headers)
1575 : * @param account_id which account to trigger process for
1576 : * @param process_row row in the legitimization processes table the legitimization is for
1577 : * @param provider_user_id user ID (or NULL) the proof is for
1578 : * @param provider_legitimization_id legitimization ID the proof is for
1579 : * @param cb function to call with the result
1580 : * @param cb_cls closure for @a cb
1581 : * @return handle to cancel operation early
1582 : */
1583 : static struct TALER_KYCLOGIC_ProofHandle *
1584 11 : oauth2_proof (void *cls,
1585 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1586 : struct MHD_Connection *connection,
1587 : const struct TALER_NormalizedPaytoHashP *account_id,
1588 : uint64_t process_row,
1589 : const char *provider_user_id,
1590 : const char *provider_legitimization_id,
1591 : TALER_KYCLOGIC_ProofCallback cb,
1592 : void *cb_cls)
1593 : {
1594 11 : struct PluginState *ps = cls;
1595 : struct TALER_KYCLOGIC_ProofHandle *ph;
1596 : const char *code;
1597 :
1598 11 : GNUNET_break (NULL == provider_user_id);
1599 11 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
1600 11 : GNUNET_snprintf (ph->provider_legitimization_id,
1601 : sizeof (ph->provider_legitimization_id),
1602 : "%llu",
1603 : (unsigned long long) process_row);
1604 11 : if ( (NULL != provider_legitimization_id) &&
1605 11 : (0 != strcmp (provider_legitimization_id,
1606 11 : ph->provider_legitimization_id)))
1607 : {
1608 0 : GNUNET_break (0);
1609 0 : GNUNET_free (ph);
1610 0 : return NULL;
1611 : }
1612 :
1613 11 : ph->pd = pd;
1614 11 : ph->connection = connection;
1615 11 : ph->h_payto = *account_id;
1616 11 : ph->cb = cb;
1617 11 : ph->cb_cls = cb_cls;
1618 11 : code = MHD_lookup_connection_value (connection,
1619 : MHD_GET_ARGUMENT_KIND,
1620 : "code");
1621 11 : if (NULL == code)
1622 : {
1623 : const char *err;
1624 : const char *desc;
1625 : const char *euri;
1626 : json_t *body;
1627 :
1628 0 : err = MHD_lookup_connection_value (connection,
1629 : MHD_GET_ARGUMENT_KIND,
1630 : "error");
1631 0 : if (NULL == err)
1632 : {
1633 0 : GNUNET_break_op (0);
1634 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
1635 0 : ph->http_status = MHD_HTTP_BAD_REQUEST;
1636 0 : body = GNUNET_JSON_PACK (
1637 : GNUNET_JSON_pack_string ("message",
1638 : "'code' parameter malformed"),
1639 : TALER_JSON_pack_ec (
1640 : TALER_EC_GENERIC_PARAMETER_MALFORMED));
1641 0 : GNUNET_break (
1642 : GNUNET_SYSERR !=
1643 : templating_build (ph->connection,
1644 : &ph->http_status,
1645 : "oauth2-bad-request",
1646 : body,
1647 : &ph->response));
1648 0 : json_decref (body);
1649 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1650 : ph);
1651 0 : return ph;
1652 : }
1653 0 : desc = MHD_lookup_connection_value (connection,
1654 : MHD_GET_ARGUMENT_KIND,
1655 : "error_description");
1656 0 : euri = MHD_lookup_connection_value (connection,
1657 : MHD_GET_ARGUMENT_KIND,
1658 : "error_uri");
1659 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1660 : "OAuth2 process %llu failed with error `%s'\n",
1661 : (unsigned long long) process_row,
1662 : err);
1663 0 : if (0 == strcasecmp (err,
1664 : "server_error"))
1665 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
1666 0 : else if (0 == strcasecmp (err,
1667 : "unauthorized_client"))
1668 0 : ph->status = TALER_KYCLOGIC_STATUS_FAILED;
1669 0 : else if (0 == strcasecmp (err,
1670 : "temporarily_unavailable"))
1671 0 : ph->status = TALER_KYCLOGIC_STATUS_PENDING;
1672 : else
1673 0 : ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
1674 0 : ph->http_status = MHD_HTTP_FORBIDDEN;
1675 0 : body = GNUNET_JSON_PACK (
1676 : GNUNET_JSON_pack_string ("error",
1677 : err),
1678 : GNUNET_JSON_pack_allow_null (
1679 : GNUNET_JSON_pack_string ("error_details",
1680 : desc)),
1681 : GNUNET_JSON_pack_allow_null (
1682 : GNUNET_JSON_pack_string ("error_uri",
1683 : euri)));
1684 0 : GNUNET_break (
1685 : GNUNET_SYSERR !=
1686 : templating_build (ph->connection,
1687 : &ph->http_status,
1688 : "oauth2-authentication-failure",
1689 : body,
1690 : &ph->response));
1691 0 : json_decref (body);
1692 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
1693 : ph);
1694 0 : return ph;
1695 :
1696 : }
1697 :
1698 11 : ph->eh = curl_easy_init ();
1699 11 : GNUNET_assert (NULL != ph->eh);
1700 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1701 : "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
1702 : pd->token_url);
1703 11 : GNUNET_assert (CURLE_OK ==
1704 : curl_easy_setopt (ph->eh,
1705 : CURLOPT_URL,
1706 : pd->token_url));
1707 11 : GNUNET_assert (CURLE_OK ==
1708 : curl_easy_setopt (ph->eh,
1709 : CURLOPT_VERBOSE,
1710 : 1));
1711 11 : GNUNET_assert (CURLE_OK ==
1712 : curl_easy_setopt (ph->eh,
1713 : CURLOPT_POST,
1714 : 1));
1715 : {
1716 : char *client_id;
1717 : char *client_secret;
1718 : char *authorization_code;
1719 : char *redirect_uri_encoded;
1720 : char *hps;
1721 :
1722 11 : hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
1723 : sizeof (ph->h_payto));
1724 : {
1725 : char *redirect_uri;
1726 :
1727 11 : GNUNET_asprintf (&redirect_uri,
1728 : "%skyc-proof/%s",
1729 : ps->exchange_base_url,
1730 11 : &pd->section[strlen ("kyc-provider-")]);
1731 11 : redirect_uri_encoded = TALER_urlencode (redirect_uri);
1732 11 : GNUNET_free (redirect_uri);
1733 : }
1734 11 : GNUNET_assert (NULL != redirect_uri_encoded);
1735 11 : client_id = curl_easy_escape (ph->eh,
1736 11 : pd->client_id,
1737 : 0);
1738 11 : GNUNET_assert (NULL != client_id);
1739 11 : client_secret = curl_easy_escape (ph->eh,
1740 11 : pd->client_secret,
1741 : 0);
1742 11 : GNUNET_assert (NULL != client_secret);
1743 11 : authorization_code = curl_easy_escape (ph->eh,
1744 : code,
1745 : 0);
1746 11 : GNUNET_assert (NULL != authorization_code);
1747 11 : GNUNET_asprintf (&ph->post_body,
1748 : "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
1749 : client_id,
1750 : redirect_uri_encoded,
1751 : hps,
1752 : client_secret,
1753 : authorization_code);
1754 11 : curl_free (authorization_code);
1755 11 : curl_free (client_secret);
1756 11 : GNUNET_free (redirect_uri_encoded);
1757 11 : GNUNET_free (hps);
1758 11 : curl_free (client_id);
1759 : }
1760 11 : GNUNET_assert (CURLE_OK ==
1761 : curl_easy_setopt (ph->eh,
1762 : CURLOPT_POSTFIELDS,
1763 : ph->post_body));
1764 11 : GNUNET_assert (CURLE_OK ==
1765 : curl_easy_setopt (ph->eh,
1766 : CURLOPT_FOLLOWLOCATION,
1767 : 1L));
1768 : /* limit MAXREDIRS to 5 as a simple security measure against
1769 : a potential infinite loop caused by a malicious target */
1770 11 : GNUNET_assert (CURLE_OK ==
1771 : curl_easy_setopt (ph->eh,
1772 : CURLOPT_MAXREDIRS,
1773 : 5L));
1774 :
1775 11 : ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
1776 : ph->eh,
1777 : &handle_curl_login_finished,
1778 : ph);
1779 11 : return ph;
1780 : }
1781 :
1782 :
1783 : /**
1784 : * Function to asynchronously return the 404 not found
1785 : * page for the webhook.
1786 : *
1787 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
1788 : */
1789 : static void
1790 0 : wh_return_not_found (void *cls)
1791 : {
1792 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1793 : struct MHD_Response *response;
1794 :
1795 0 : wh->task = NULL;
1796 0 : response = MHD_create_response_from_buffer_static (0,
1797 : "");
1798 0 : wh->cb (wh->cb_cls,
1799 : 0LLU,
1800 : NULL,
1801 : false,
1802 : NULL,
1803 : NULL,
1804 : NULL,
1805 : TALER_KYCLOGIC_STATUS_KEEP,
1806 0 : GNUNET_TIME_UNIT_ZERO_ABS,
1807 : NULL,
1808 : MHD_HTTP_NOT_FOUND,
1809 : response);
1810 0 : GNUNET_free (wh);
1811 0 : }
1812 :
1813 :
1814 : /**
1815 : * Check KYC status and return result for Webhook.
1816 : *
1817 : * @param cls the @e cls of this struct with the plugin-specific state
1818 : * @param pd provider configuration details
1819 : * @param plc callback to lookup accounts with
1820 : * @param plc_cls closure for @a plc
1821 : * @param http_method HTTP method used for the webhook
1822 : * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
1823 : * @param connection MHD connection object (for HTTP headers)
1824 : * @param body HTTP request body, or NULL if not available
1825 : * @param cb function to call with the result
1826 : * @param cb_cls closure for @a cb
1827 : * @return handle to cancel operation early
1828 : */
1829 : static struct TALER_KYCLOGIC_WebhookHandle *
1830 0 : oauth2_webhook (void *cls,
1831 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1832 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1833 : void *plc_cls,
1834 : const char *http_method,
1835 : const char *const url_path[],
1836 : struct MHD_Connection *connection,
1837 : const json_t *body,
1838 : TALER_KYCLOGIC_WebhookCallback cb,
1839 : void *cb_cls)
1840 : {
1841 0 : struct PluginState *ps = cls;
1842 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1843 :
1844 : (void) pd;
1845 : (void) plc;
1846 : (void) plc_cls;
1847 : (void) http_method;
1848 : (void) url_path;
1849 : (void) connection;
1850 : (void) body;
1851 0 : GNUNET_break_op (0);
1852 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1853 0 : wh->cb = cb;
1854 0 : wh->cb_cls = cb_cls;
1855 0 : wh->ps = ps;
1856 0 : wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
1857 : wh);
1858 0 : return wh;
1859 : }
1860 :
1861 :
1862 : /**
1863 : * Cancel KYC webhook execution.
1864 : *
1865 : * @param[in] wh handle of operation to cancel
1866 : */
1867 : static void
1868 0 : oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
1869 : {
1870 0 : GNUNET_SCHEDULER_cancel (wh->task);
1871 0 : GNUNET_free (wh);
1872 0 : }
1873 :
1874 :
1875 : /**
1876 : * Initialize OAuth2.0 KYC logic plugin
1877 : *
1878 : * @param cls a configuration instance
1879 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
1880 : */
1881 : void *
1882 : libtaler_plugin_kyclogic_oauth2_init (void *cls);
1883 :
1884 : /* declaration to avoid compiler warning */
1885 : void *
1886 61 : libtaler_plugin_kyclogic_oauth2_init (void *cls)
1887 : {
1888 61 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
1889 : struct TALER_KYCLOGIC_Plugin *plugin;
1890 : struct PluginState *ps;
1891 :
1892 61 : ps = GNUNET_new (struct PluginState);
1893 61 : ps->cfg = cfg;
1894 61 : if (GNUNET_OK !=
1895 61 : GNUNET_CONFIGURATION_get_value_string (cfg,
1896 : "exchange",
1897 : "BASE_URL",
1898 : &ps->exchange_base_url))
1899 : {
1900 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1901 : "exchange",
1902 : "BASE_URL");
1903 0 : GNUNET_free (ps);
1904 0 : return NULL;
1905 : }
1906 : ps->curl_ctx
1907 122 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1908 61 : &ps->curl_rc);
1909 61 : if (NULL == ps->curl_ctx)
1910 : {
1911 0 : GNUNET_break (0);
1912 0 : GNUNET_free (ps->exchange_base_url);
1913 0 : GNUNET_free (ps);
1914 0 : return NULL;
1915 : }
1916 61 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
1917 :
1918 61 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
1919 61 : plugin->cls = ps;
1920 : plugin->load_configuration
1921 61 : = &oauth2_load_configuration;
1922 : plugin->unload_configuration
1923 61 : = &oauth2_unload_configuration;
1924 : plugin->initiate
1925 61 : = &oauth2_initiate;
1926 : plugin->initiate_cancel
1927 61 : = &oauth2_initiate_cancel;
1928 : plugin->proof
1929 61 : = &oauth2_proof;
1930 : plugin->proof_cancel
1931 61 : = &oauth2_proof_cancel;
1932 : plugin->webhook
1933 61 : = &oauth2_webhook;
1934 : plugin->webhook_cancel
1935 61 : = &oauth2_webhook_cancel;
1936 61 : return plugin;
1937 : }
1938 :
1939 :
1940 : /**
1941 : * Unload authorization plugin
1942 : *
1943 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
1944 : * @return NULL (always)
1945 : */
1946 : void *
1947 : libtaler_plugin_kyclogic_oauth2_done (void *cls);
1948 :
1949 : /* declaration to avoid compiler warning */
1950 : void *
1951 61 : libtaler_plugin_kyclogic_oauth2_done (void *cls)
1952 : {
1953 61 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
1954 61 : struct PluginState *ps = plugin->cls;
1955 :
1956 61 : if (NULL != ps->curl_ctx)
1957 : {
1958 61 : GNUNET_CURL_fini (ps->curl_ctx);
1959 61 : ps->curl_ctx = NULL;
1960 : }
1961 61 : if (NULL != ps->curl_rc)
1962 : {
1963 61 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
1964 61 : ps->curl_rc = NULL;
1965 : }
1966 61 : GNUNET_free (ps->exchange_base_url);
1967 61 : GNUNET_free (ps);
1968 61 : GNUNET_free (plugin);
1969 61 : return NULL;
1970 : }
|