Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : Copyright (C) 2022 Taler Systems SA
4 :
5 : Taler is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : Taler is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : Taler; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file plugin_kyclogic_oauth2.c
18 : * @brief oauth2.0 based authentication flow logic
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler_kyclogic_plugin.h"
23 : #include "taler_mhd_lib.h"
24 : #include "taler_json_lib.h"
25 : #include <regex.h>
26 : #include "taler_util.h"
27 :
28 :
29 : /**
30 : * Saves the state of a plugin.
31 : */
32 : struct PluginState
33 : {
34 :
35 : /**
36 : * Our global configuration.
37 : */
38 : const struct GNUNET_CONFIGURATION_Handle *cfg;
39 :
40 : /**
41 : * Our base URL.
42 : */
43 : char *exchange_base_url;
44 :
45 : /**
46 : * Context for CURL operations (useful to the event loop)
47 : */
48 : struct GNUNET_CURL_Context *curl_ctx;
49 :
50 : /**
51 : * Context for integrating @e curl_ctx with the
52 : * GNUnet event loop.
53 : */
54 : struct GNUNET_CURL_RescheduleContext *curl_rc;
55 :
56 : };
57 :
58 :
59 : /**
60 : * Keeps the plugin-specific state for
61 : * a given configuration section.
62 : */
63 : struct TALER_KYCLOGIC_ProviderDetails
64 : {
65 :
66 : /**
67 : * Overall plugin state.
68 : */
69 : struct PluginState *ps;
70 :
71 : /**
72 : * Configuration section that configured us.
73 : */
74 : char *section;
75 :
76 : /**
77 : * URL of the OAuth2.0 endpoint for KYC checks.
78 : * (token/auth)
79 : */
80 : char *auth_url;
81 :
82 : /**
83 : * URL of the OAuth2.0 endpoint for KYC checks.
84 : */
85 : char *login_url;
86 :
87 : /**
88 : * URL of the user info access endpoint.
89 : */
90 : char *info_url;
91 :
92 : /**
93 : * Our client ID for OAuth2.0.
94 : */
95 : char *client_id;
96 :
97 : /**
98 : * Our client secret for OAuth2.0.
99 : */
100 : char *client_secret;
101 :
102 : /**
103 : * Where to redirect clients after the
104 : * Web-based KYC process is done?
105 : */
106 : char *post_kyc_redirect_url;
107 :
108 : /**
109 : * Validity time for a successful KYC process.
110 : */
111 : struct GNUNET_TIME_Relative validity;
112 :
113 : };
114 :
115 :
116 : /**
117 : * Handle for an initiation operation.
118 : */
119 : struct TALER_KYCLOGIC_InitiateHandle
120 : {
121 :
122 : /**
123 : * Hash of the payto:// URI we are initiating
124 : * the KYC for.
125 : */
126 : struct TALER_PaytoHashP h_payto;
127 :
128 : /**
129 : * UUID being checked.
130 : */
131 : uint64_t legitimization_uuid;
132 :
133 : /**
134 : * Our configuration details.
135 : */
136 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
137 :
138 : /**
139 : * The task for asynchronous response generation.
140 : */
141 : struct GNUNET_SCHEDULER_Task *task;
142 :
143 : /**
144 : * Continuation to call.
145 : */
146 : TALER_KYCLOGIC_InitiateCallback cb;
147 :
148 : /**
149 : * Closure for @a cb.
150 : */
151 : void *cb_cls;
152 :
153 : };
154 :
155 :
156 : /**
157 : * Handle for an KYC proof operation.
158 : */
159 : struct TALER_KYCLOGIC_ProofHandle
160 : {
161 :
162 : /**
163 : * Our configuration details.
164 : */
165 : const struct TALER_KYCLOGIC_ProviderDetails *pd;
166 :
167 : /**
168 : * HTTP connection we are processing.
169 : */
170 : struct MHD_Connection *connection;
171 :
172 : /**
173 : * Hash of the payto URI that this is about.
174 : */
175 : struct TALER_PaytoHashP h_payto;
176 :
177 : /**
178 : * Continuation to call.
179 : */
180 : TALER_KYCLOGIC_ProofCallback cb;
181 :
182 : /**
183 : * Closure for @e cb.
184 : */
185 : void *cb_cls;
186 :
187 : /**
188 : * Curl request we are running to the OAuth 2.0 service.
189 : */
190 : CURL *eh;
191 :
192 : /**
193 : * Body for the @e eh POST request.
194 : */
195 : char *post_body;
196 :
197 : /**
198 : * Response to return.
199 : */
200 : struct MHD_Response *response;
201 :
202 : /**
203 : * The task for asynchronous response generation.
204 : */
205 : struct GNUNET_SCHEDULER_Task *task;
206 :
207 : /**
208 : * Handle for the OAuth 2.0 CURL request.
209 : */
210 : struct GNUNET_CURL_Job *job;
211 :
212 : /**
213 : * User ID to return, the 'id' from OAuth.
214 : */
215 : char *provider_user_id;
216 :
217 : /**
218 : * Legitimization ID to return, the 64-bit row ID
219 : * as a string.
220 : */
221 : char provider_legitimization_id[32];
222 :
223 : /**
224 : * KYC status to return.
225 : */
226 : enum TALER_KYCLOGIC_KycStatus status;
227 :
228 : /**
229 : * HTTP status to return.
230 : */
231 : unsigned int http_status;
232 :
233 :
234 : };
235 :
236 :
237 : /**
238 : * Handle for an KYC Web hook operation.
239 : */
240 : struct TALER_KYCLOGIC_WebhookHandle
241 : {
242 :
243 : /**
244 : * Continuation to call when done.
245 : */
246 : TALER_KYCLOGIC_WebhookCallback cb;
247 :
248 : /**
249 : * Closure for @a cb.
250 : */
251 : void *cb_cls;
252 :
253 : /**
254 : * Task for asynchronous execution.
255 : */
256 : struct GNUNET_SCHEDULER_Task *task;
257 :
258 : /**
259 : * Overall plugin state.
260 : */
261 : struct PluginState *ps;
262 : };
263 :
264 :
265 : /**
266 : * Release configuration resources previously loaded
267 : *
268 : * @param[in] pd configuration to release
269 : */
270 : static void
271 0 : oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
272 : {
273 0 : GNUNET_free (pd->section);
274 0 : GNUNET_free (pd->auth_url);
275 0 : GNUNET_free (pd->login_url);
276 0 : GNUNET_free (pd->info_url);
277 0 : GNUNET_free (pd->client_id);
278 0 : GNUNET_free (pd->client_secret);
279 0 : GNUNET_free (pd->post_kyc_redirect_url);
280 0 : GNUNET_free (pd);
281 0 : }
282 :
283 :
284 : /**
285 : * Load the configuration of the KYC provider.
286 : *
287 : * @param cls closure
288 : * @param provider_section_name configuration section to parse
289 : * @return NULL if configuration is invalid
290 : */
291 : static struct TALER_KYCLOGIC_ProviderDetails *
292 1 : oauth2_load_configuration (void *cls,
293 : const char *provider_section_name)
294 : {
295 1 : struct PluginState *ps = cls;
296 : struct TALER_KYCLOGIC_ProviderDetails *pd;
297 : char *s;
298 :
299 1 : pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
300 1 : pd->ps = ps;
301 1 : pd->section = GNUNET_strdup (provider_section_name);
302 1 : if (GNUNET_OK !=
303 1 : GNUNET_CONFIGURATION_get_value_time (ps->cfg,
304 : provider_section_name,
305 : "KYC_OAUTH2_VALIDITY",
306 : &pd->validity))
307 : {
308 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
309 : provider_section_name,
310 : "KYC_OAUTH2_VALIDITY");
311 0 : oauth2_unload_configuration (pd);
312 0 : return NULL;
313 : }
314 1 : if (GNUNET_OK !=
315 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
316 : provider_section_name,
317 : "KYC_OAUTH2_AUTH_URL",
318 : &s))
319 : {
320 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
321 : provider_section_name,
322 : "KYC_OAUTH2_AUTH_URL");
323 0 : oauth2_unload_configuration (pd);
324 0 : return NULL;
325 : }
326 1 : if ( (! TALER_url_valid_charset (s)) ||
327 1 : ( (0 != strncasecmp (s,
328 : "http://",
329 0 : strlen ("http://"))) &&
330 0 : (0 != strncasecmp (s,
331 : "https://",
332 : strlen ("https://"))) ) )
333 : {
334 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
335 : provider_section_name,
336 : "KYC_OAUTH2_AUTH_URL",
337 : "not a valid URL");
338 0 : GNUNET_free (s);
339 0 : oauth2_unload_configuration (pd);
340 0 : return NULL;
341 : }
342 1 : pd->auth_url = s;
343 :
344 1 : if (GNUNET_OK !=
345 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
346 : provider_section_name,
347 : "KYC_OAUTH2_LOGIN_URL",
348 : &s))
349 : {
350 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
351 : provider_section_name,
352 : "KYC_OAUTH2_LOGIN_URL");
353 0 : oauth2_unload_configuration (pd);
354 0 : return NULL;
355 : }
356 1 : if ( (! TALER_url_valid_charset (s)) ||
357 1 : ( (0 != strncasecmp (s,
358 : "http://",
359 0 : strlen ("http://"))) &&
360 0 : (0 != strncasecmp (s,
361 : "https://",
362 : strlen ("https://"))) ) )
363 : {
364 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
365 : provider_section_name,
366 : "KYC_OAUTH2_LOGIN_URL",
367 : "not a valid URL");
368 0 : oauth2_unload_configuration (pd);
369 0 : GNUNET_free (s);
370 0 : return NULL;
371 : }
372 1 : pd->login_url = s;
373 :
374 1 : if (GNUNET_OK !=
375 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
376 : provider_section_name,
377 : "KYC_OAUTH2_INFO_URL",
378 : &s))
379 : {
380 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
381 : provider_section_name,
382 : "KYC_OAUTH2_INFO_URL");
383 0 : oauth2_unload_configuration (pd);
384 0 : return NULL;
385 : }
386 1 : if ( (! TALER_url_valid_charset (s)) ||
387 1 : ( (0 != strncasecmp (s,
388 : "http://",
389 0 : strlen ("http://"))) &&
390 0 : (0 != strncasecmp (s,
391 : "https://",
392 : strlen ("https://"))) ) )
393 : {
394 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
395 : provider_section_name,
396 : "KYC_INFO_URL",
397 : "not a valid URL");
398 0 : GNUNET_free (s);
399 0 : oauth2_unload_configuration (pd);
400 0 : return NULL;
401 : }
402 1 : pd->info_url = s;
403 :
404 1 : if (GNUNET_OK !=
405 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
406 : provider_section_name,
407 : "KYC_OAUTH2_CLIENT_ID",
408 : &s))
409 : {
410 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
411 : provider_section_name,
412 : "KYC_OAUTH2_CLIENT_ID");
413 0 : oauth2_unload_configuration (pd);
414 0 : return NULL;
415 : }
416 1 : pd->client_id = s;
417 :
418 1 : if (GNUNET_OK !=
419 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
420 : provider_section_name,
421 : "KYC_OAUTH2_CLIENT_SECRET",
422 : &s))
423 : {
424 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
425 : provider_section_name,
426 : "KYC_OAUTH2_CLIENT_SECRET");
427 0 : oauth2_unload_configuration (pd);
428 0 : return NULL;
429 : }
430 1 : pd->client_secret = s;
431 :
432 1 : if (GNUNET_OK !=
433 1 : GNUNET_CONFIGURATION_get_value_string (ps->cfg,
434 : provider_section_name,
435 : "KYC_OAUTH2_POST_URL",
436 : &s))
437 : {
438 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
439 : provider_section_name,
440 : "KYC_OAUTH2_POST_URL");
441 0 : oauth2_unload_configuration (pd);
442 0 : return NULL;
443 : }
444 1 : pd->post_kyc_redirect_url = s;
445 :
446 1 : return pd;
447 : }
448 :
449 :
450 : /**
451 : * Logic to asynchronously return the response for
452 : * how to begin the OAuth2.0 checking process to
453 : * the client.
454 : *
455 : * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
456 : */
457 : static void
458 0 : initiate_task (void *cls)
459 : {
460 0 : struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
461 0 : const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
462 0 : struct PluginState *ps = pd->ps;
463 : char *hps;
464 : char *url;
465 : char *redirect_uri;
466 : char *redirect_uri_encoded;
467 : char legi_s[42];
468 :
469 0 : ih->task = NULL;
470 0 : GNUNET_snprintf (legi_s,
471 : sizeof (legi_s),
472 : "%llu",
473 0 : (unsigned long long) ih->legitimization_uuid);
474 0 : hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
475 : sizeof (ih->h_payto));
476 0 : GNUNET_asprintf (&redirect_uri,
477 : "%s/kyc-proof/%s/%s/%s",
478 : ps->exchange_base_url,
479 : hps,
480 : pd->section,
481 : legi_s);
482 0 : redirect_uri_encoded = TALER_urlencode (redirect_uri);
483 0 : GNUNET_free (redirect_uri);
484 0 : GNUNET_asprintf (&url,
485 : "%s?client_id=%s&redirect_uri=%s",
486 : pd->login_url,
487 : pd->client_id,
488 : redirect_uri_encoded);
489 0 : GNUNET_free (redirect_uri_encoded);
490 0 : ih->cb (ih->cb_cls,
491 : TALER_EC_NONE,
492 : url,
493 : NULL /* unknown user_id here */,
494 : legi_s,
495 : NULL /* no error */);
496 0 : GNUNET_free (url);
497 0 : GNUNET_free (hps);
498 0 : GNUNET_free (ih);
499 0 : }
500 :
501 :
502 : /**
503 : * Initiate KYC check.
504 : *
505 : * @param cls the @e cls of this struct with the plugin-specific state
506 : * @param pd provider configuration details
507 : * @param account_id which account to trigger process for
508 : * @param legitimization_uuid unique ID for the legitimization process
509 : * @param cb function to call with the result
510 : * @param cb_cls closure for @a cb
511 : * @return handle to cancel operation early
512 : */
513 : static struct TALER_KYCLOGIC_InitiateHandle *
514 0 : oauth2_initiate (void *cls,
515 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
516 : const struct TALER_PaytoHashP *account_id,
517 : uint64_t legitimization_uuid,
518 : TALER_KYCLOGIC_InitiateCallback cb,
519 : void *cb_cls)
520 : {
521 : struct TALER_KYCLOGIC_InitiateHandle *ih;
522 :
523 : (void) cls;
524 0 : ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
525 0 : ih->legitimization_uuid = legitimization_uuid;
526 0 : ih->cb = cb;
527 0 : ih->cb_cls = cb_cls;
528 0 : ih->h_payto = *account_id;
529 0 : ih->pd = pd;
530 0 : ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
531 : ih);
532 0 : return ih;
533 : }
534 :
535 :
536 : /**
537 : * Cancel KYC check initiation.
538 : *
539 : * @param[in] ih handle of operation to cancel
540 : */
541 : static void
542 0 : oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
543 : {
544 0 : if (NULL != ih->task)
545 : {
546 0 : GNUNET_SCHEDULER_cancel (ih->task);
547 0 : ih->task = NULL;
548 : }
549 0 : GNUNET_free (ih);
550 0 : }
551 :
552 :
553 : /**
554 : * Function called to asynchronously return the final
555 : * result to the callback.
556 : *
557 : * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
558 : */
559 : static void
560 0 : return_proof_response (void *cls)
561 : {
562 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
563 :
564 0 : ph->task = NULL;
565 0 : ph->cb (ph->cb_cls,
566 : ph->status,
567 0 : ph->provider_user_id,
568 0 : ph->provider_legitimization_id,
569 0 : GNUNET_TIME_relative_to_absolute (ph->pd->validity),
570 : ph->http_status,
571 : ph->response);
572 0 : GNUNET_free (ph->provider_user_id);
573 0 : GNUNET_free (ph);
574 0 : }
575 :
576 :
577 : /**
578 : * The request for @a ph failed. We may have gotten a useful error
579 : * message in @a j. Generate a failure response.
580 : *
581 : * @param[in,out] ph request that failed
582 : * @param j reply from the server (or NULL)
583 : */
584 : static void
585 0 : handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
586 : const json_t *j)
587 : {
588 : const char *msg;
589 : const char *desc;
590 : struct GNUNET_JSON_Specification spec[] = {
591 0 : GNUNET_JSON_spec_string ("error",
592 : &msg),
593 0 : GNUNET_JSON_spec_string ("error_description",
594 : &desc),
595 0 : GNUNET_JSON_spec_end ()
596 : };
597 :
598 : {
599 : enum GNUNET_GenericReturnValue res;
600 : const char *emsg;
601 : unsigned int line;
602 :
603 0 : res = GNUNET_JSON_parse (j,
604 : spec,
605 : &emsg,
606 : &line);
607 0 : if (GNUNET_OK != res)
608 : {
609 0 : GNUNET_break_op (0);
610 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
611 : ph->response
612 0 : = TALER_MHD_make_error (
613 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
614 : "Unexpected response from KYC gateway");
615 : ph->http_status
616 0 : = MHD_HTTP_BAD_GATEWAY;
617 0 : return;
618 : }
619 : }
620 : /* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED,
621 : we MAY want to in the future look at the requested content type
622 : and possibly respond in JSON if indicated. */
623 : {
624 : char *reply;
625 :
626 0 : GNUNET_asprintf (&reply,
627 : "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>",
628 : msg,
629 : msg,
630 : desc);
631 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
632 : ph->response
633 0 : = MHD_create_response_from_buffer (strlen (reply),
634 : reply,
635 : MHD_RESPMEM_MUST_COPY);
636 0 : GNUNET_assert (NULL != ph->response);
637 0 : GNUNET_free (reply);
638 : }
639 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
640 0 : ph->http_status = MHD_HTTP_FORBIDDEN;
641 : }
642 :
643 :
644 : /**
645 : * The request for @a ph succeeded (presumably).
646 : * Call continuation with the result.
647 : *
648 : * @param[in,out] ph request that succeeded
649 : * @param j reply from the server
650 : */
651 : static void
652 0 : parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
653 : const json_t *j)
654 : {
655 : const char *state;
656 : json_t *data;
657 : struct GNUNET_JSON_Specification spec[] = {
658 0 : GNUNET_JSON_spec_string ("status",
659 : &state),
660 0 : GNUNET_JSON_spec_json ("data",
661 : &data),
662 0 : GNUNET_JSON_spec_end ()
663 : };
664 : enum GNUNET_GenericReturnValue res;
665 : const char *emsg;
666 : unsigned int line;
667 :
668 0 : res = GNUNET_JSON_parse (j,
669 : spec,
670 : &emsg,
671 : &line);
672 0 : if (GNUNET_OK != res)
673 : {
674 0 : GNUNET_break_op (0);
675 0 : json_dumpf (j,
676 : stderr,
677 : JSON_INDENT (2));
678 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
679 : ph->response
680 0 : = TALER_MHD_make_error (
681 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
682 : "Unexpected response from KYC gateway");
683 : ph->http_status
684 0 : = MHD_HTTP_BAD_GATEWAY;
685 0 : return;
686 : }
687 0 : if (0 != strcasecmp (state,
688 : "success"))
689 : {
690 0 : GNUNET_break_op (0);
691 0 : handle_proof_error (ph,
692 : j);
693 0 : return;
694 : }
695 : {
696 : const char *id;
697 : struct GNUNET_JSON_Specification ispec[] = {
698 0 : GNUNET_JSON_spec_string ("id",
699 : &id),
700 0 : GNUNET_JSON_spec_end ()
701 : };
702 :
703 0 : res = GNUNET_JSON_parse (data,
704 : ispec,
705 : &emsg,
706 : &line);
707 0 : if (GNUNET_OK != res)
708 : {
709 0 : GNUNET_break_op (0);
710 0 : json_dumpf (data,
711 : stderr,
712 : JSON_INDENT (2));
713 0 : ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
714 : ph->response
715 0 : = TALER_MHD_make_error (
716 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
717 : "Unexpected response from KYC gateway");
718 : ph->http_status
719 0 : = MHD_HTTP_BAD_GATEWAY;
720 0 : return;
721 : }
722 0 : ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
723 0 : ph->response = MHD_create_response_from_buffer (0,
724 : "",
725 : MHD_RESPMEM_PERSISTENT);
726 0 : GNUNET_assert (NULL != ph->response);
727 0 : GNUNET_break (MHD_YES ==
728 : MHD_add_response_header (
729 : ph->response,
730 : MHD_HTTP_HEADER_LOCATION,
731 : ph->pd->post_kyc_redirect_url));
732 0 : ph->http_status = MHD_HTTP_SEE_OTHER;
733 0 : ph->provider_user_id = GNUNET_strdup (id);
734 : }
735 : }
736 :
737 :
738 : /**
739 : * After we are done with the CURL interaction we
740 : * need to update our database state with the information
741 : * retrieved.
742 : *
743 : * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
744 : * @param response_code HTTP response code from server, 0 on hard error
745 : * @param response in JSON, NULL if response was not in JSON format
746 : */
747 : static void
748 0 : handle_curl_proof_finished (void *cls,
749 : long response_code,
750 : const void *response)
751 : {
752 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
753 0 : const json_t *j = response;
754 :
755 0 : ph->job = NULL;
756 0 : switch (response_code)
757 : {
758 0 : case MHD_HTTP_OK:
759 0 : parse_proof_success_reply (ph,
760 : j);
761 0 : break;
762 0 : default:
763 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
764 : "OAuth2.0 info URL returned HTTP status %u\n",
765 : (unsigned int) response_code);
766 0 : handle_proof_error (ph,
767 : j);
768 0 : break;
769 : }
770 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
771 : ph);
772 0 : }
773 :
774 :
775 : /**
776 : * After we are done with the CURL interaction we
777 : * need to fetch the user's account details.
778 : *
779 : * @param cls our `struct KycProofContext`
780 : * @param response_code HTTP response code from server, 0 on hard error
781 : * @param response in JSON, NULL if response was not in JSON format
782 : */
783 : static void
784 0 : handle_curl_login_finished (void *cls,
785 : long response_code,
786 : const void *response)
787 : {
788 0 : struct TALER_KYCLOGIC_ProofHandle *ph = cls;
789 0 : const json_t *j = response;
790 :
791 0 : ph->job = NULL;
792 0 : switch (response_code)
793 : {
794 0 : case MHD_HTTP_OK:
795 : {
796 : const char *access_token;
797 : const char *token_type;
798 : uint64_t expires_in_s;
799 : const char *refresh_token;
800 : struct GNUNET_JSON_Specification spec[] = {
801 0 : GNUNET_JSON_spec_string ("access_token",
802 : &access_token),
803 0 : GNUNET_JSON_spec_string ("token_type",
804 : &token_type),
805 0 : GNUNET_JSON_spec_uint64 ("expires_in",
806 : &expires_in_s),
807 0 : GNUNET_JSON_spec_string ("refresh_token",
808 : &refresh_token),
809 0 : GNUNET_JSON_spec_end ()
810 : };
811 : CURL *eh;
812 :
813 : {
814 : enum GNUNET_GenericReturnValue res;
815 : const char *emsg;
816 : unsigned int line;
817 :
818 0 : res = GNUNET_JSON_parse (j,
819 : spec,
820 : &emsg,
821 : &line);
822 0 : if (GNUNET_OK != res)
823 : {
824 0 : GNUNET_break_op (0);
825 : ph->response
826 0 : = TALER_MHD_make_error (
827 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
828 : "Unexpected response from KYC gateway");
829 : ph->http_status
830 0 : = MHD_HTTP_BAD_GATEWAY;
831 0 : break;
832 : }
833 : }
834 0 : if (0 != strcasecmp (token_type,
835 : "bearer"))
836 : {
837 0 : GNUNET_break_op (0);
838 : ph->response
839 0 : = TALER_MHD_make_error (
840 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
841 : "Unexpected token type in response from KYC gateway");
842 : ph->http_status
843 0 : = MHD_HTTP_BAD_GATEWAY;
844 0 : break;
845 : }
846 :
847 : /* We guard against a few characters that could
848 : conceivably be abused to mess with the HTTP header */
849 0 : if ( (NULL != strchr (access_token,
850 0 : '\n')) ||
851 0 : (NULL != strchr (access_token,
852 0 : '\r')) ||
853 0 : (NULL != strchr (access_token,
854 0 : ' ')) ||
855 0 : (NULL != strchr (access_token,
856 : ';')) )
857 : {
858 0 : GNUNET_break_op (0);
859 : ph->response
860 0 : = TALER_MHD_make_error (
861 : TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
862 : "Illegal character in access token");
863 : ph->http_status
864 0 : = MHD_HTTP_BAD_GATEWAY;
865 0 : break;
866 : }
867 :
868 0 : eh = curl_easy_init ();
869 0 : if (NULL == eh)
870 : {
871 0 : GNUNET_break_op (0);
872 : ph->response
873 0 : = TALER_MHD_make_error (
874 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
875 : "curl_easy_init");
876 : ph->http_status
877 0 : = MHD_HTTP_INTERNAL_SERVER_ERROR;
878 0 : break;
879 : }
880 0 : GNUNET_assert (CURLE_OK ==
881 : curl_easy_setopt (eh,
882 : CURLOPT_URL,
883 : ph->pd->info_url));
884 : {
885 : char *hdr;
886 : struct curl_slist *slist;
887 :
888 0 : GNUNET_asprintf (&hdr,
889 : "%s: Bearer %s",
890 : MHD_HTTP_HEADER_AUTHORIZATION,
891 : access_token);
892 0 : slist = curl_slist_append (NULL,
893 : hdr);
894 0 : ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
895 : eh,
896 : slist,
897 : &handle_curl_proof_finished,
898 : ph);
899 0 : curl_slist_free_all (slist);
900 0 : GNUNET_free (hdr);
901 : }
902 0 : return;
903 : }
904 0 : default:
905 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
906 : "OAuth2.0 login URL returned HTTP status %u\n",
907 : (unsigned int) response_code);
908 0 : handle_proof_error (ph,
909 : j);
910 0 : break;
911 : }
912 0 : return_proof_response (ph);
913 : }
914 :
915 :
916 : /**
917 : * Check KYC status and return status to human.
918 : *
919 : * @param cls the @e cls of this struct with the plugin-specific state
920 : * @param pd provider configuration details
921 : * @param url_path rest of the URL after `/kyc-webhook/`
922 : * @param connection MHD connection object (for HTTP headers)
923 : * @param account_id which account to trigger process for
924 : * @param process_row row in the legitimization processes table the legitimization is for
925 : * @param provider_user_id user ID (or NULL) the proof is for
926 : * @param provider_legitimization_id legitimization ID the proof is for
927 : * @param cb function to call with the result
928 : * @param cb_cls closure for @a cb
929 : * @return handle to cancel operation early
930 : */
931 : static struct TALER_KYCLOGIC_ProofHandle *
932 0 : oauth2_proof (void *cls,
933 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
934 : const char *const url_path[],
935 : struct MHD_Connection *connection,
936 : const struct TALER_PaytoHashP *account_id,
937 : uint64_t process_row,
938 : const char *provider_user_id,
939 : const char *provider_legitimization_id,
940 : TALER_KYCLOGIC_ProofCallback cb,
941 : void *cb_cls)
942 : {
943 0 : struct PluginState *ps = cls;
944 : struct TALER_KYCLOGIC_ProofHandle *ph;
945 : const char *code;
946 :
947 : (void) url_path;
948 0 : GNUNET_break (NULL == provider_user_id);
949 0 : ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
950 0 : GNUNET_snprintf (ph->provider_legitimization_id,
951 : sizeof (ph->provider_legitimization_id),
952 : "%llu",
953 : (unsigned long long) process_row);
954 0 : if ( (NULL != provider_legitimization_id) &&
955 0 : (0 != strcmp (provider_legitimization_id,
956 0 : ph->provider_legitimization_id)))
957 : {
958 0 : GNUNET_break (0);
959 0 : GNUNET_free (ph);
960 0 : return NULL;
961 : }
962 0 : ph->pd = pd;
963 0 : ph->connection = connection;
964 0 : ph->h_payto = *account_id;
965 0 : ph->cb = cb;
966 0 : ph->cb_cls = cb_cls;
967 0 : code = MHD_lookup_connection_value (connection,
968 : MHD_GET_ARGUMENT_KIND,
969 : "code");
970 0 : if (NULL == code)
971 : {
972 0 : GNUNET_break_op (0);
973 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
974 0 : ph->http_status = MHD_HTTP_BAD_REQUEST;
975 0 : ph->response = TALER_MHD_make_error (
976 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
977 : "code");
978 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
979 : ph);
980 0 : return ph;
981 : }
982 :
983 0 : ph->eh = curl_easy_init ();
984 0 : if (NULL == ph->eh)
985 : {
986 0 : GNUNET_break (0);
987 0 : ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
988 0 : ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
989 0 : ph->response = TALER_MHD_make_error (
990 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
991 : "curl_easy_init");
992 0 : ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
993 : ph);
994 0 : return ph;
995 : }
996 :
997 0 : GNUNET_assert (CURLE_OK ==
998 : curl_easy_setopt (ph->eh,
999 : CURLOPT_URL,
1000 : pd->auth_url));
1001 0 : GNUNET_assert (CURLE_OK ==
1002 : curl_easy_setopt (ph->eh,
1003 : CURLOPT_POST,
1004 : 1));
1005 : {
1006 : char *client_id;
1007 : char *redirect_uri;
1008 : char *client_secret;
1009 : char *authorization_code;
1010 :
1011 0 : client_id = curl_easy_escape (ph->eh,
1012 0 : pd->client_id,
1013 : 0);
1014 0 : GNUNET_assert (NULL != client_id);
1015 : {
1016 : char *request_uri;
1017 :
1018 0 : GNUNET_asprintf (&request_uri,
1019 : "%s?client_id=%s",
1020 : pd->login_url,
1021 : pd->client_id);
1022 0 : redirect_uri = curl_easy_escape (ph->eh,
1023 : request_uri,
1024 : 0);
1025 0 : GNUNET_free (request_uri);
1026 : }
1027 0 : GNUNET_assert (NULL != redirect_uri);
1028 0 : client_secret = curl_easy_escape (ph->eh,
1029 0 : pd->client_secret,
1030 : 0);
1031 0 : GNUNET_assert (NULL != client_secret);
1032 0 : authorization_code = curl_easy_escape (ph->eh,
1033 : code,
1034 : 0);
1035 0 : GNUNET_assert (NULL != authorization_code);
1036 0 : GNUNET_asprintf (&ph->post_body,
1037 : "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code",
1038 : client_id,
1039 : redirect_uri,
1040 : client_secret,
1041 : authorization_code);
1042 0 : curl_free (authorization_code);
1043 0 : curl_free (client_secret);
1044 0 : curl_free (redirect_uri);
1045 0 : curl_free (client_id);
1046 : }
1047 0 : GNUNET_assert (CURLE_OK ==
1048 : curl_easy_setopt (ph->eh,
1049 : CURLOPT_POSTFIELDS,
1050 : ph->post_body));
1051 0 : GNUNET_assert (CURLE_OK ==
1052 : curl_easy_setopt (ph->eh,
1053 : CURLOPT_FOLLOWLOCATION,
1054 : 1L));
1055 : /* limit MAXREDIRS to 5 as a simple security measure against
1056 : a potential infinite loop caused by a malicious target */
1057 0 : GNUNET_assert (CURLE_OK ==
1058 : curl_easy_setopt (ph->eh,
1059 : CURLOPT_MAXREDIRS,
1060 : 5L));
1061 :
1062 0 : ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
1063 : ph->eh,
1064 : &handle_curl_login_finished,
1065 : ph);
1066 0 : return ph;
1067 : }
1068 :
1069 :
1070 : /**
1071 : * Cancel KYC proof.
1072 : *
1073 : * @param[in] ph handle of operation to cancel
1074 : */
1075 : static void
1076 0 : oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
1077 : {
1078 0 : if (NULL != ph->task)
1079 : {
1080 0 : GNUNET_SCHEDULER_cancel (ph->task);
1081 0 : ph->task = NULL;
1082 : }
1083 0 : if (NULL != ph->job)
1084 : {
1085 0 : GNUNET_CURL_job_cancel (ph->job);
1086 0 : ph->job = NULL;
1087 : }
1088 0 : if (NULL != ph->response)
1089 : {
1090 0 : MHD_destroy_response (ph->response);
1091 0 : ph->response = NULL;
1092 : }
1093 0 : GNUNET_free (ph->post_body);
1094 0 : GNUNET_free (ph);
1095 0 : }
1096 :
1097 :
1098 : /**
1099 : * Function to asynchronously return the 404 not found
1100 : * page for the webhook.
1101 : *
1102 : * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
1103 : */
1104 : static void
1105 0 : wh_return_not_found (void *cls)
1106 : {
1107 0 : struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
1108 : struct MHD_Response *response;
1109 :
1110 0 : wh->task = NULL;
1111 0 : response = MHD_create_response_from_buffer (0,
1112 : "",
1113 : MHD_RESPMEM_PERSISTENT);
1114 0 : wh->cb (wh->cb_cls,
1115 : 0LLU,
1116 : NULL,
1117 : NULL,
1118 : NULL,
1119 : NULL,
1120 : TALER_KYCLOGIC_STATUS_KEEP,
1121 0 : GNUNET_TIME_UNIT_ZERO_ABS,
1122 : MHD_HTTP_NOT_FOUND,
1123 : response);
1124 0 : GNUNET_free (wh);
1125 0 : }
1126 :
1127 :
1128 : /**
1129 : * Check KYC status and return result for Webhook.
1130 : *
1131 : * @param cls the @e cls of this struct with the plugin-specific state
1132 : * @param pd provider configuration details
1133 : * @param plc callback to lookup accounts with
1134 : * @param plc_cls closure for @a plc
1135 : * @param http_method HTTP method used for the webhook
1136 : * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
1137 : * @param connection MHD connection object (for HTTP headers)
1138 : * @param body HTTP request body, or NULL if not available
1139 : * @param cb function to call with the result
1140 : * @param cb_cls closure for @a cb
1141 : * @return handle to cancel operation early
1142 : */
1143 : static struct TALER_KYCLOGIC_WebhookHandle *
1144 0 : oauth2_webhook (void *cls,
1145 : const struct TALER_KYCLOGIC_ProviderDetails *pd,
1146 : TALER_KYCLOGIC_ProviderLookupCallback plc,
1147 : void *plc_cls,
1148 : const char *http_method,
1149 : const char *const url_path[],
1150 : struct MHD_Connection *connection,
1151 : const json_t *body,
1152 : TALER_KYCLOGIC_WebhookCallback cb,
1153 : void *cb_cls)
1154 : {
1155 0 : struct PluginState *ps = cls;
1156 : struct TALER_KYCLOGIC_WebhookHandle *wh;
1157 :
1158 : (void) pd;
1159 : (void) plc;
1160 : (void) plc_cls;
1161 : (void) http_method;
1162 : (void) url_path;
1163 : (void) connection;
1164 : (void) body;
1165 0 : wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
1166 0 : wh->cb = cb;
1167 0 : wh->cb_cls = cb_cls;
1168 0 : wh->ps = ps;
1169 0 : wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
1170 : wh);
1171 0 : return wh;
1172 : }
1173 :
1174 :
1175 : /**
1176 : * Cancel KYC webhook execution.
1177 : *
1178 : * @param[in] wh handle of operation to cancel
1179 : */
1180 : static void
1181 0 : oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
1182 : {
1183 0 : GNUNET_SCHEDULER_cancel (wh->task);
1184 0 : GNUNET_free (wh);
1185 0 : }
1186 :
1187 :
1188 : /**
1189 : * Initialize OAuth2.0 KYC logic plugin
1190 : *
1191 : * @param cls a configuration instance
1192 : * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
1193 : */
1194 : void *
1195 1 : libtaler_plugin_kyclogic_oauth2_init (void *cls)
1196 : {
1197 1 : const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
1198 : struct TALER_KYCLOGIC_Plugin *plugin;
1199 : struct PluginState *ps;
1200 :
1201 1 : ps = GNUNET_new (struct PluginState);
1202 1 : ps->cfg = cfg;
1203 1 : if (GNUNET_OK !=
1204 1 : GNUNET_CONFIGURATION_get_value_string (cfg,
1205 : "exchange",
1206 : "BASE_URL",
1207 : &ps->exchange_base_url))
1208 : {
1209 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
1210 : "exchange",
1211 : "BASE_URL");
1212 0 : GNUNET_free (ps);
1213 0 : return NULL;
1214 : }
1215 : ps->curl_ctx
1216 2 : = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1217 1 : &ps->curl_rc);
1218 1 : if (NULL == ps->curl_ctx)
1219 : {
1220 0 : GNUNET_break (0);
1221 0 : GNUNET_free (ps->exchange_base_url);
1222 0 : GNUNET_free (ps);
1223 0 : return NULL;
1224 : }
1225 1 : ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
1226 :
1227 1 : plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
1228 1 : plugin->cls = ps;
1229 : plugin->load_configuration
1230 1 : = &oauth2_load_configuration;
1231 : plugin->unload_configuration
1232 1 : = &oauth2_unload_configuration;
1233 : plugin->initiate
1234 1 : = &oauth2_initiate;
1235 : plugin->initiate_cancel
1236 1 : = &oauth2_initiate_cancel;
1237 : plugin->proof
1238 1 : = &oauth2_proof;
1239 : plugin->proof_cancel
1240 1 : = &oauth2_proof_cancel;
1241 : plugin->webhook
1242 1 : = &oauth2_webhook;
1243 : plugin->webhook_cancel
1244 1 : = &oauth2_webhook_cancel;
1245 1 : return plugin;
1246 : }
1247 :
1248 :
1249 : /**
1250 : * Unload authorization plugin
1251 : *
1252 : * @param cls a `struct TALER_KYCLOGIC_Plugin`
1253 : * @return NULL (always)
1254 : */
1255 : void *
1256 0 : libtaler_plugin_kyclogic_oauth2_done (void *cls)
1257 : {
1258 0 : struct TALER_KYCLOGIC_Plugin *plugin = cls;
1259 0 : struct PluginState *ps = plugin->cls;
1260 :
1261 0 : if (NULL != ps->curl_ctx)
1262 : {
1263 0 : GNUNET_CURL_fini (ps->curl_ctx);
1264 0 : ps->curl_ctx = NULL;
1265 : }
1266 0 : if (NULL != ps->curl_rc)
1267 : {
1268 0 : GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
1269 0 : ps->curl_rc = NULL;
1270 : }
1271 0 : GNUNET_free (ps->exchange_base_url);
1272 0 : GNUNET_free (ps);
1273 0 : GNUNET_free (plugin);
1274 0 : return NULL;
1275 : }
|