Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2021-2023 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. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_kyc-proof.c
18 : * @brief Handle request for proof for KYC check.
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <gnunet/gnunet_json_lib.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h>
26 : #include "taler/taler_attributes.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/taler_kyclogic_lib.h"
29 : #include "taler/taler_mhd_lib.h"
30 : #include "taler/taler_templating_lib.h"
31 : #include "taler-exchange-httpd_common_kyc.h"
32 : #include "taler-exchange-httpd_kyc-proof.h"
33 : #include "taler-exchange-httpd_responses.h"
34 :
35 :
36 : /**
37 : * Context for the proof.
38 : */
39 : struct KycProofContext
40 : {
41 :
42 : /**
43 : * Kept in a DLL while suspended.
44 : */
45 : struct KycProofContext *next;
46 :
47 : /**
48 : * Kept in a DLL while suspended.
49 : */
50 : struct KycProofContext *prev;
51 :
52 : /**
53 : * Details about the connection we are processing.
54 : */
55 : struct TEH_RequestContext *rc;
56 :
57 : /**
58 : * Proof logic to run.
59 : */
60 : struct TALER_KYCLOGIC_Plugin *logic;
61 :
62 : /**
63 : * Configuration for @a logic.
64 : */
65 : struct TALER_KYCLOGIC_ProviderDetails *pd;
66 :
67 : /**
68 : * Asynchronous operation with the proof system.
69 : */
70 : struct TALER_KYCLOGIC_ProofHandle *ph;
71 :
72 : /**
73 : * KYC AML trigger operation.
74 : */
75 : struct TEH_KycMeasureRunContext *kat;
76 :
77 : /**
78 : * Process information about the user for the plugin from the database, can
79 : * be NULL.
80 : */
81 : char *provider_user_id;
82 :
83 : /**
84 : * Process information about the legitimization process for the plugin from the
85 : * database, can be NULL.
86 : */
87 : char *provider_legitimization_id;
88 :
89 : /**
90 : * Hash of payment target URI this is about.
91 : */
92 : struct TALER_NormalizedPaytoHashP h_payto;
93 :
94 : /**
95 : * Final HTTP response to return.
96 : */
97 : struct MHD_Response *response;
98 :
99 : /**
100 : * Final HTTP response code to return.
101 : */
102 : unsigned int response_code;
103 :
104 : /**
105 : * HTTP response from the KYC provider plugin.
106 : */
107 : struct MHD_Response *proof_response;
108 :
109 : /**
110 : * HTTP response code from the KYC provider plugin.
111 : */
112 : unsigned int proof_response_code;
113 :
114 : /**
115 : * Provider configuration section name of the logic we are running.
116 : */
117 : const char *provider_name;
118 :
119 : /**
120 : * Row in the database for this legitimization operation.
121 : */
122 : uint64_t process_row;
123 :
124 : /**
125 : * True if we are suspended,
126 : */
127 : bool suspended;
128 :
129 : /**
130 : * True if @e h_payto is for a wallet.
131 : */
132 : bool is_wallet;
133 :
134 : };
135 :
136 :
137 : /**
138 : * Contexts are kept in a DLL while suspended.
139 : */
140 : static struct KycProofContext *kpc_head;
141 :
142 : /**
143 : * Contexts are kept in a DLL while suspended.
144 : */
145 : static struct KycProofContext *kpc_tail;
146 :
147 :
148 : /**
149 : * Generate HTML error for @a connection using @a template.
150 : *
151 : * @param connection HTTP client connection
152 : * @param template template to expand
153 : * @param[in,out] http_status HTTP status of the response
154 : * @param ec Taler error code to return
155 : * @param message extended message to return
156 : * @return MHD response object
157 : */
158 : static struct MHD_Response *
159 1 : make_html_error (struct MHD_Connection *connection,
160 : const char *template,
161 : unsigned int *http_status,
162 : enum TALER_ErrorCode ec,
163 : const char *message)
164 : {
165 1 : struct MHD_Response *response = NULL;
166 : json_t *body;
167 :
168 1 : body = GNUNET_JSON_PACK (
169 : GNUNET_JSON_pack_allow_null (
170 : GNUNET_JSON_pack_string ("message",
171 : message)),
172 : TALER_JSON_pack_ec (
173 : ec));
174 1 : GNUNET_break (
175 : GNUNET_SYSERR !=
176 : TALER_TEMPLATING_build (connection,
177 : http_status,
178 : template,
179 : NULL,
180 : NULL,
181 : body,
182 : &response));
183 1 : json_decref (body);
184 1 : return response;
185 : }
186 :
187 :
188 : /**
189 : * Resume processing the @a kpc request.
190 : *
191 : * @param kpc request to resume
192 : */
193 : static void
194 11 : kpc_resume (struct KycProofContext *kpc)
195 : {
196 11 : GNUNET_assert (GNUNET_YES == kpc->suspended);
197 11 : kpc->suspended = false;
198 11 : GNUNET_CONTAINER_DLL_remove (kpc_head,
199 : kpc_tail,
200 : kpc);
201 11 : MHD_resume_connection (kpc->rc->connection);
202 11 : TALER_MHD_daemon_trigger ();
203 11 : }
204 :
205 :
206 : void
207 21 : TEH_kyc_proof_cleanup (void)
208 : {
209 : struct KycProofContext *kpc;
210 :
211 21 : while (NULL != (kpc = kpc_head))
212 : {
213 0 : if (NULL != kpc->ph)
214 : {
215 0 : kpc->logic->proof_cancel (kpc->ph);
216 0 : kpc->ph = NULL;
217 : }
218 0 : kpc_resume (kpc);
219 : }
220 21 : }
221 :
222 :
223 : /**
224 : * Function called after the KYC-AML trigger is done.
225 : *
226 : * @param cls closure
227 : * @param ec error code or 0 on success
228 : * @param detail error message or NULL on success / no info
229 : */
230 : static void
231 11 : proof_finish (
232 : void *cls,
233 : enum TALER_ErrorCode ec,
234 : const char *detail)
235 : {
236 11 : struct KycProofContext *kpc = cls;
237 :
238 11 : kpc->kat = NULL;
239 11 : if (TALER_EC_NONE != ec)
240 : {
241 1 : kpc->response_code = TALER_ErrorCode_get_http_status (ec);
242 1 : GNUNET_break (5 != kpc->response_code / 100);
243 1 : GNUNET_assert (kpc->response_code != UINT_MAX);
244 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
245 : "Templating error response for %d and HTTP status %u (%s)\n",
246 : (int) ec,
247 : kpc->response_code,
248 : detail);
249 1 : kpc->response = make_html_error (
250 1 : kpc->rc->connection,
251 : "kyc-proof-internal-error",
252 : &kpc->response_code,
253 : ec,
254 : detail);
255 : }
256 : else
257 : {
258 10 : GNUNET_assert (NULL != kpc->proof_response);
259 10 : kpc->response_code = kpc->proof_response_code;
260 10 : kpc->response = kpc->proof_response;
261 10 : kpc->proof_response = NULL;
262 10 : kpc->proof_response_code = 0;
263 : }
264 11 : GNUNET_assert (NULL != kpc->response);
265 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
266 : "Resuming with response %p and status %u\n",
267 : kpc->response,
268 : kpc->response_code);
269 11 : kpc_resume (kpc);
270 11 : }
271 :
272 :
273 : /**
274 : * Respond with an HTML message on the given @a rc.
275 : *
276 : * @param[in,out] rc request to respond to
277 : * @param http_status HTTP status code to use
278 : * @param template template to fill in
279 : * @param ec error code to use for the template
280 : * @param message additional message to return
281 : * @return MHD result code
282 : */
283 : static MHD_RESULT
284 0 : respond_html_ec (struct TEH_RequestContext *rc,
285 : unsigned int http_status,
286 : const char *template,
287 : enum TALER_ErrorCode ec,
288 : const char *message)
289 : {
290 : struct MHD_Response *response;
291 : MHD_RESULT res;
292 :
293 0 : response = make_html_error (rc->connection,
294 : template,
295 : &http_status,
296 : ec,
297 : message);
298 0 : res = MHD_queue_response (rc->connection,
299 : http_status,
300 : response);
301 0 : MHD_destroy_response (response);
302 0 : return res;
303 : }
304 :
305 :
306 : /**
307 : * Function called with the result of a proof check operation.
308 : *
309 : * Note that the "decref" for the @a response
310 : * will be done by the callee and MUST NOT be done by the plugin.
311 : *
312 : * @param cls closure
313 : * @param status KYC status
314 : * @param provider_name name of the provider
315 : * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
316 : * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
317 : * @param expiration until when is the KYC check valid
318 : * @param attributes user attributes returned by the provider
319 : * @param http_status HTTP status code of @a response
320 : * @param[in] response to return to the HTTP client
321 : */
322 : static void
323 11 : proof_cb (
324 : void *cls,
325 : enum TALER_KYCLOGIC_KycStatus status,
326 : const char *provider_name,
327 : const char *provider_user_id,
328 : const char *provider_legitimization_id,
329 : struct GNUNET_TIME_Absolute expiration,
330 : const json_t *attributes,
331 : unsigned int http_status,
332 : struct MHD_Response *response)
333 : {
334 11 : struct KycProofContext *kpc = cls;
335 11 : struct TEH_RequestContext *rc = kpc->rc;
336 : struct GNUNET_AsyncScopeSave old_scope;
337 : enum GNUNET_DB_QueryStatus qs;
338 :
339 11 : kpc->ph = NULL;
340 11 : kpc->proof_response = response;
341 11 : kpc->proof_response_code = http_status;
342 11 : GNUNET_async_scope_enter (&rc->async_scope_id,
343 : &old_scope);
344 11 : switch (status)
345 : {
346 9 : case TALER_KYCLOGIC_STATUS_SUCCESS:
347 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
348 : "KYC process #%llu succeeded with KYC provider\n",
349 : (unsigned long long) kpc->process_row);
350 9 : qs = TEH_kyc_store_attributes (
351 : kpc->process_row,
352 9 : &kpc->h_payto,
353 : provider_name,
354 : provider_user_id,
355 : provider_legitimization_id,
356 : expiration,
357 : attributes);
358 9 : if (0 >= qs)
359 : {
360 0 : GNUNET_break (0);
361 0 : proof_finish (kpc,
362 : TALER_EC_GENERIC_DB_STORE_FAILED,
363 : "kyc_store_attributes");
364 0 : break;
365 : }
366 :
367 18 : kpc->kat = TEH_kyc_run_measure_for_attributes (
368 9 : &rc->async_scope_id,
369 : kpc->process_row,
370 9 : &kpc->h_payto,
371 9 : kpc->is_wallet,
372 : &proof_finish,
373 : kpc);
374 9 : if (NULL == kpc->kat)
375 : {
376 0 : GNUNET_break_op (0);
377 0 : proof_finish (kpc,
378 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
379 : NULL);
380 : }
381 9 : break;
382 2 : case TALER_KYCLOGIC_STATUS_FAILED:
383 : case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
384 : case TALER_KYCLOGIC_STATUS_USER_ABORTED:
385 : case TALER_KYCLOGIC_STATUS_ABORTED:
386 2 : GNUNET_assert (NULL == kpc->kat);
387 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
388 : "KYC process %s/%s (Row #%llu) failed: %d\n",
389 : provider_user_id,
390 : provider_legitimization_id,
391 : (unsigned long long) kpc->process_row,
392 : status);
393 2 : if (5 == http_status / 100)
394 : {
395 : char *msg;
396 :
397 : /* OAuth2 server had a problem, do NOT log this as a KYC failure */
398 1 : GNUNET_break (0);
399 1 : GNUNET_asprintf (&msg,
400 : "Failure by KYC provider (HTTP status %u)\n",
401 : http_status);
402 1 : proof_finish (
403 : kpc,
404 : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
405 : msg);
406 1 : GNUNET_free (msg);
407 1 : break;
408 : }
409 2 : if (! TEH_kyc_failed (
410 : kpc->process_row,
411 1 : &kpc->h_payto,
412 : kpc->provider_name,
413 : provider_user_id,
414 : provider_legitimization_id,
415 : TALER_KYCLOGIC_status2s (status),
416 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
417 : {
418 0 : GNUNET_break (0);
419 0 : proof_finish (
420 : kpc,
421 : TALER_EC_GENERIC_DB_STORE_FAILED,
422 : "TEH_kyc_failed");
423 0 : break;
424 : }
425 1 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
426 : "KYC process #%llu failed with status %d\n",
427 : (unsigned long long) kpc->process_row,
428 : status);
429 1 : proof_finish (kpc,
430 : TALER_EC_NONE,
431 : NULL);
432 1 : break;
433 0 : default:
434 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
435 : "KYC status of %s/%s (Row #%llu) is %d\n",
436 : provider_user_id,
437 : provider_legitimization_id,
438 : (unsigned long long) kpc->process_row,
439 : (int) status);
440 0 : break;
441 : }
442 11 : GNUNET_async_scope_restore (&old_scope);
443 11 : }
444 :
445 :
446 : /**
447 : * Function called to clean up a context.
448 : *
449 : * @param rc request context
450 : */
451 : static void
452 11 : clean_kpc (struct TEH_RequestContext *rc)
453 : {
454 11 : struct KycProofContext *kpc = rc->rh_ctx;
455 :
456 11 : if (NULL != kpc->ph)
457 : {
458 0 : kpc->logic->proof_cancel (kpc->ph);
459 0 : kpc->ph = NULL;
460 : }
461 11 : if (NULL != kpc->kat)
462 : {
463 0 : TEH_kyc_run_measure_cancel (kpc->kat);
464 0 : kpc->kat = NULL;
465 : }
466 11 : if (NULL != kpc->response)
467 : {
468 11 : MHD_destroy_response (kpc->response);
469 11 : kpc->response = NULL;
470 : }
471 11 : if (NULL != kpc->proof_response)
472 : {
473 1 : MHD_destroy_response (kpc->proof_response);
474 1 : kpc->proof_response = NULL;
475 : }
476 11 : GNUNET_free (kpc->provider_user_id);
477 11 : GNUNET_free (kpc->provider_legitimization_id);
478 11 : GNUNET_free (kpc);
479 11 : }
480 :
481 :
482 : MHD_RESULT
483 22 : TEH_handler_kyc_proof (
484 : struct TEH_RequestContext *rc,
485 : const char *const args[1])
486 : {
487 22 : struct KycProofContext *kpc = rc->rh_ctx;
488 22 : const char *provider_name_or_logic = args[0];
489 :
490 22 : if (NULL == kpc)
491 : {
492 : /* first time */
493 11 : if (NULL == provider_name_or_logic)
494 : {
495 0 : GNUNET_break_op (0);
496 0 : return respond_html_ec (
497 : rc,
498 : MHD_HTTP_NOT_FOUND,
499 : "kyc-proof-endpoint-unknown",
500 : TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
501 : "'/kyc-proof/$PROVIDER_NAME?state=$H_PAYTO' required");
502 : }
503 11 : kpc = GNUNET_new (struct KycProofContext);
504 11 : kpc->rc = rc;
505 11 : rc->rh_ctx = kpc;
506 11 : rc->rh_cleaner = &clean_kpc;
507 11 : TALER_MHD_parse_request_arg_auto_t (rc->connection,
508 : "state",
509 : &kpc->h_payto);
510 11 : if (GNUNET_OK !=
511 11 : TALER_KYCLOGIC_lookup_logic (
512 : provider_name_or_logic,
513 : &kpc->logic,
514 : &kpc->pd,
515 : &kpc->provider_name))
516 : {
517 0 : GNUNET_break_op (0);
518 0 : return respond_html_ec (
519 : rc,
520 : MHD_HTTP_NOT_FOUND,
521 : "kyc-proof-target-unknown",
522 : TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
523 : provider_name_or_logic);
524 : }
525 11 : if (NULL != kpc->provider_name)
526 : {
527 : enum GNUNET_DB_QueryStatus qs;
528 : struct GNUNET_TIME_Absolute expiration;
529 :
530 11 : if (0 != strcmp (provider_name_or_logic,
531 : kpc->provider_name))
532 : {
533 0 : GNUNET_break_op (0);
534 0 : return respond_html_ec (
535 : rc,
536 : MHD_HTTP_BAD_REQUEST,
537 : "kyc-proof-bad-request",
538 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
539 : "PROVIDER_NAME");
540 : }
541 :
542 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
543 : "Looking for KYC process at %s\n",
544 : kpc->provider_name);
545 11 : qs = TEH_plugin->lookup_kyc_process_by_account (
546 11 : TEH_plugin->cls,
547 : kpc->provider_name,
548 11 : &kpc->h_payto,
549 : &kpc->process_row,
550 : &expiration,
551 : &kpc->provider_user_id,
552 : &kpc->provider_legitimization_id,
553 : &kpc->is_wallet);
554 11 : switch (qs)
555 : {
556 0 : case GNUNET_DB_STATUS_HARD_ERROR:
557 : case GNUNET_DB_STATUS_SOFT_ERROR:
558 0 : GNUNET_break (0);
559 0 : return respond_html_ec (
560 : rc,
561 : MHD_HTTP_INTERNAL_SERVER_ERROR,
562 : "kyc-proof-internal-error",
563 : TALER_EC_GENERIC_DB_FETCH_FAILED,
564 : "lookup_kyc_process_by_account");
565 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
566 0 : GNUNET_break_op (0);
567 0 : return respond_html_ec (
568 : rc,
569 : MHD_HTTP_NOT_FOUND,
570 : "kyc-proof-target-unknown",
571 : TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
572 : kpc->provider_name);
573 11 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
574 11 : break;
575 : }
576 11 : if (GNUNET_TIME_absolute_is_future (expiration))
577 : {
578 : /* KYC not required */
579 0 : return respond_html_ec (
580 : rc,
581 : MHD_HTTP_OK,
582 : "kyc-proof-already-done",
583 : TALER_EC_NONE,
584 : NULL);
585 : }
586 : }
587 22 : kpc->ph = kpc->logic->proof (
588 11 : kpc->logic->cls,
589 11 : kpc->pd,
590 : rc->connection,
591 11 : &kpc->h_payto,
592 : kpc->process_row,
593 11 : kpc->provider_user_id,
594 11 : kpc->provider_legitimization_id,
595 : &proof_cb,
596 : kpc);
597 11 : if (NULL == kpc->ph)
598 : {
599 0 : GNUNET_break (0);
600 0 : return respond_html_ec (
601 : rc,
602 : MHD_HTTP_INTERNAL_SERVER_ERROR,
603 : "kyc-proof-internal-error",
604 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
605 : "could not start proof with KYC logic");
606 : }
607 :
608 :
609 11 : kpc->suspended = true;
610 11 : GNUNET_CONTAINER_DLL_insert (kpc_head,
611 : kpc_tail,
612 : kpc);
613 11 : MHD_suspend_connection (rc->connection);
614 11 : return MHD_YES;
615 : }
616 :
617 11 : if (NULL == kpc->response)
618 : {
619 0 : GNUNET_break (0);
620 0 : return respond_html_ec (
621 : rc,
622 : MHD_HTTP_INTERNAL_SERVER_ERROR,
623 : "kyc-proof-internal-error",
624 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
625 : "handler resumed without response");
626 : }
627 :
628 : /* return response from KYC logic */
629 11 : return MHD_queue_response (rc->connection,
630 : kpc->response_code,
631 : kpc->response);
632 : }
633 :
634 :
635 : /* end of taler-exchange-httpd_kyc-proof.c */
|