Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2021-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. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_kyc-check.c
18 : * @brief Handle request for generic 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 <pthread.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_signatures.h"
31 : #include "taler/taler_dbevents.h"
32 : #include "taler-exchange-httpd_keys.h"
33 : #include "taler-exchange-httpd_kyc-check.h"
34 : #include "taler-exchange-httpd_kyc-wallet.h"
35 : #include "taler-exchange-httpd_responses.h"
36 :
37 : /**
38 : * Reserve GET request that is long-polling.
39 : */
40 : struct KycPoller
41 : {
42 : /**
43 : * Kept in a DLL.
44 : */
45 : struct KycPoller *next;
46 :
47 : /**
48 : * Kept in a DLL.
49 : */
50 : struct KycPoller *prev;
51 :
52 : /**
53 : * Connection we are handling.
54 : */
55 : struct MHD_Connection *connection;
56 :
57 : /**
58 : * Subscription for the database event we are
59 : * waiting for.
60 : */
61 : struct GNUNET_DB_EventHandler *eh;
62 :
63 : /**
64 : * Account for which we perform the KYC check.
65 : */
66 : struct TALER_NormalizedPaytoHashP h_payto;
67 :
68 : /**
69 : * When will this request time out?
70 : */
71 : struct GNUNET_TIME_Absolute timeout;
72 :
73 : /**
74 : * Signature by the account owner authorizing this
75 : * operation.
76 : */
77 : union TALER_AccountSignatureP account_sig;
78 :
79 : /**
80 : * Public key from the account owner authorizing this
81 : * operation. Optional, see @e have_pub.
82 : */
83 : union TALER_AccountPublicKeyP account_pub;
84 :
85 : /**
86 : * Generation of KYC rules already known to the client
87 : * (when long-polling). Do not send these rules again.
88 : */
89 : uint64_t min_rule;
90 :
91 : /**
92 : * What are we long-polling for (if anything)?
93 : */
94 : enum TALER_EXCHANGE_KycLongPollTarget lpt;
95 :
96 : /**
97 : * True if we are still suspended.
98 : */
99 : bool suspended;
100 :
101 : /**
102 : * True if we have an @e account_pub.
103 : */
104 : bool have_pub;
105 :
106 : };
107 :
108 :
109 : /**
110 : * Head of list of requests in long polling.
111 : */
112 : static struct KycPoller *kyp_head;
113 :
114 : /**
115 : * Tail of list of requests in long polling.
116 : */
117 : static struct KycPoller *kyp_tail;
118 :
119 :
120 : void
121 21 : TEH_kyc_check_cleanup ()
122 : {
123 : struct KycPoller *kyp;
124 :
125 42 : while (NULL != (kyp = kyp_head))
126 : {
127 0 : GNUNET_CONTAINER_DLL_remove (kyp_head,
128 : kyp_tail,
129 : kyp);
130 0 : if (kyp->suspended)
131 : {
132 0 : kyp->suspended = false;
133 0 : MHD_resume_connection (kyp->connection);
134 : }
135 : }
136 21 : }
137 :
138 :
139 : /**
140 : * Function called once a connection is done to
141 : * clean up the `struct ReservePoller` state.
142 : *
143 : * @param rc context to clean up for
144 : */
145 : static void
146 16 : kyp_cleanup (struct TEH_RequestContext *rc)
147 : {
148 16 : struct KycPoller *kyp = rc->rh_ctx;
149 :
150 16 : GNUNET_assert (! kyp->suspended);
151 16 : if (NULL != kyp->eh)
152 : {
153 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
154 : "Cancelling DB event listening\n");
155 14 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
156 : kyp->eh);
157 14 : kyp->eh = NULL;
158 : }
159 16 : GNUNET_free (kyp);
160 16 : }
161 :
162 :
163 : /**
164 : * Function called on events received from Postgres.
165 : * Wakes up long pollers.
166 : *
167 : * @param cls the `struct TEH_RequestContext *`
168 : * @param extra additional event data provided
169 : * @param extra_size number of bytes in @a extra
170 : */
171 : static void
172 0 : db_event_cb (void *cls,
173 : const void *extra,
174 : size_t extra_size)
175 : {
176 0 : struct TEH_RequestContext *rc = cls;
177 0 : struct KycPoller *kyp = rc->rh_ctx;
178 : struct GNUNET_AsyncScopeSave old_scope;
179 :
180 : (void) extra;
181 : (void) extra_size;
182 0 : if (! kyp->suspended)
183 0 : return; /* event triggered while main transaction
184 : was still running, or got multiple wake-up events */
185 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
186 : "Received KYC update event\n");
187 0 : kyp->suspended = false;
188 0 : GNUNET_async_scope_enter (&rc->async_scope_id,
189 : &old_scope);
190 0 : TEH_check_invariants ();
191 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
192 : "Resuming from long-polling on KYC status\n");
193 0 : GNUNET_CONTAINER_DLL_remove (kyp_head,
194 : kyp_tail,
195 : kyp);
196 0 : MHD_resume_connection (kyp->connection);
197 0 : TALER_MHD_daemon_trigger ();
198 0 : TEH_check_invariants ();
199 0 : GNUNET_async_scope_restore (&old_scope);
200 : }
201 :
202 :
203 : MHD_RESULT
204 16 : TEH_handler_kyc_check (
205 : struct TEH_RequestContext *rc,
206 : const char *const args[1])
207 : {
208 16 : struct KycPoller *kyp = rc->rh_ctx;
209 16 : json_t *jrules = NULL;
210 16 : json_t *jlimits = NULL;
211 : union TALER_AccountPublicKeyP reserve_pub;
212 : struct TALER_AccountAccessTokenP access_token;
213 : bool aml_review;
214 : bool kyc_required;
215 16 : bool access_ok = false;
216 : bool is_wallet;
217 16 : uint64_t rule_gen = 0;
218 :
219 16 : if (NULL == kyp)
220 : {
221 16 : bool sig_required = true;
222 :
223 16 : kyp = GNUNET_new (struct KycPoller);
224 16 : kyp->connection = rc->connection;
225 16 : rc->rh_ctx = kyp;
226 16 : rc->rh_cleaner = &kyp_cleanup;
227 :
228 16 : if (GNUNET_OK !=
229 16 : GNUNET_STRINGS_string_to_data (args[0],
230 : strlen (args[0]),
231 16 : &kyp->h_payto,
232 : sizeof (kyp->h_payto)))
233 : {
234 0 : GNUNET_break_op (0);
235 0 : return TALER_MHD_reply_with_error (
236 : rc->connection,
237 : MHD_HTTP_BAD_REQUEST,
238 : TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
239 : "h_payto");
240 : }
241 16 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
242 : "Checking KYC status for normalized payto hash %s\n",
243 : args[0]);
244 16 : TALER_MHD_parse_request_header_auto (
245 : rc->connection,
246 : TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
247 : &kyp->account_sig,
248 : sig_required);
249 16 : TALER_MHD_parse_request_header_auto (
250 : rc->connection,
251 : TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
252 : &kyp->account_pub,
253 : kyp->have_pub);
254 16 : TALER_MHD_parse_request_timeout (rc->connection,
255 : &kyp->timeout);
256 : {
257 16 : uint64_t num = 0;
258 : int val;
259 :
260 16 : TALER_MHD_parse_request_number (rc->connection,
261 : "lpt",
262 : &num);
263 16 : val = (int) num;
264 16 : if ( (val < 0) ||
265 : (val > TALER_EXCHANGE_KLPT_MAX) )
266 : {
267 : /* Protocol violation, but we can be graceful and
268 : just ignore the long polling! */
269 0 : GNUNET_break_op (0);
270 0 : val = TALER_EXCHANGE_KLPT_NONE;
271 : }
272 16 : kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
273 16 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
274 : "Long polling for target %d with timeout %s\n",
275 : val,
276 : GNUNET_TIME_relative2s (
277 : GNUNET_TIME_absolute_get_remaining (
278 : kyp->timeout),
279 : true));
280 : }
281 16 : TALER_MHD_parse_request_number (rc->connection,
282 : "min_rule",
283 : &kyp->min_rule);
284 : /* long polling needed? */
285 16 : if (GNUNET_TIME_absolute_is_future (kyp->timeout))
286 : {
287 14 : struct TALER_KycCompletedEventP rep = {
288 14 : .header.size = htons (sizeof (rep)),
289 14 : .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
290 : .h_payto = kyp->h_payto
291 : };
292 :
293 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
294 : "Starting DB event listening\n");
295 28 : kyp->eh = TEH_plugin->event_listen (
296 14 : TEH_plugin->cls,
297 : GNUNET_TIME_absolute_get_remaining (kyp->timeout),
298 : &rep.header,
299 : &db_event_cb,
300 : rc);
301 : }
302 : } /* end initialization */
303 :
304 16 : if (! TEH_enable_kyc)
305 : {
306 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
307 : "KYC not enabled\n");
308 2 : return TALER_MHD_reply_static (
309 : rc->connection,
310 : MHD_HTTP_NO_CONTENT,
311 : NULL,
312 : NULL,
313 : 0);
314 : }
315 :
316 : {
317 : enum GNUNET_DB_QueryStatus qs;
318 : bool do_suspend;
319 :
320 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
321 : "Looking up KYC requirements for account %s (%s)\n",
322 : TALER_B2S (&kyp->h_payto),
323 : kyp->have_pub ? "with account-pub" : "legacy wallet");
324 14 : qs = TEH_plugin->lookup_kyc_requirement_by_row (
325 14 : TEH_plugin->cls,
326 14 : &kyp->h_payto,
327 14 : kyp->have_pub,
328 : &kyp->account_pub,
329 : &is_wallet,
330 : &reserve_pub.reserve_pub,
331 : &access_token,
332 : &rule_gen,
333 : &jrules,
334 : &aml_review,
335 : &kyc_required);
336 14 : if (qs < 0)
337 : {
338 0 : GNUNET_break (0);
339 0 : return TALER_MHD_reply_with_ec (
340 : rc->connection,
341 : TALER_EC_GENERIC_DB_STORE_FAILED,
342 : "lookup_kyc_requirement_by_row");
343 : }
344 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
345 : "Found rule %llu (client wants > %llu)\n",
346 : (unsigned long long) rule_gen,
347 : (unsigned long long) kyp->min_rule);
348 14 : do_suspend = false;
349 14 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
350 : {
351 : /* account unknown */
352 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
353 : "Account unknown!\n");
354 0 : if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
355 0 : (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
356 0 : (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
357 : {
358 0 : do_suspend = true;
359 0 : access_ok = true; /* for now */
360 : }
361 : }
362 : else
363 : {
364 14 : access_ok =
365 28 : ( (! GNUNET_is_zero (&kyp->account_pub) &&
366 : (GNUNET_OK ==
367 14 : TALER_account_kyc_auth_verify (&kyp->account_pub,
368 28 : &kyp->account_sig)) ) ||
369 0 : (! GNUNET_is_zero (&reserve_pub) &&
370 : (GNUNET_OK ==
371 0 : TALER_account_kyc_auth_verify (&reserve_pub,
372 0 : &kyp->account_sig)) ) );
373 14 : if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
374 14 : (rule_gen <= kyp->min_rule) )
375 : {
376 12 : switch (kyp->lpt)
377 : {
378 12 : case TALER_EXCHANGE_KLPT_NONE:
379 12 : break;
380 0 : case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
381 0 : if (! access_ok)
382 0 : do_suspend = true;
383 0 : break;
384 0 : case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
385 0 : if (! aml_review)
386 0 : do_suspend = true;
387 0 : break;
388 0 : case TALER_EXCHANGE_KLPT_KYC_OK:
389 0 : if (kyc_required)
390 0 : do_suspend = true;
391 0 : break;
392 : }
393 : }
394 : }
395 :
396 14 : if (do_suspend &&
397 0 : (access_ok ||
398 0 : (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
399 : {
400 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
401 : "Suspending HTTP request on timeout (%s) for %d\n",
402 : GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
403 : kyp->timeout),
404 : true),
405 : (int) kyp->lpt);
406 0 : GNUNET_assert (NULL != kyp->eh);
407 0 : kyp->suspended = true;
408 0 : GNUNET_CONTAINER_DLL_insert (kyp_head,
409 : kyp_tail,
410 : kyp);
411 0 : MHD_suspend_connection (kyp->connection);
412 0 : return MHD_YES;
413 : }
414 14 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
415 : {
416 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
417 : "Returning account unknown\n");
418 0 : return TALER_MHD_reply_with_error (
419 : rc->connection,
420 : MHD_HTTP_NOT_FOUND,
421 : TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
422 : NULL);
423 : }
424 : }
425 :
426 14 : if (! access_ok)
427 : {
428 0 : json_decref (jrules);
429 0 : jrules = NULL;
430 0 : if (GNUNET_is_zero (&kyp->account_pub))
431 : {
432 0 : GNUNET_break_op (0);
433 0 : return TALER_MHD_reply_with_error (
434 : rc->connection,
435 : MHD_HTTP_CONFLICT,
436 : TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
437 : NULL);
438 : }
439 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
440 : "Returning authorization failed\n");
441 0 : return TALER_MHD_REPLY_JSON_PACK (
442 : rc->connection,
443 : MHD_HTTP_FORBIDDEN,
444 : TALER_JSON_pack_ec (
445 : TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
446 : GNUNET_JSON_pack_data_auto ("expected_account_pub",
447 : &kyp->account_pub));
448 : }
449 :
450 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
451 : "KYC rules apply to %s:\n",
452 : is_wallet ? "wallet" : "account");
453 14 : if (NULL != jrules)
454 4 : json_dumpf (jrules,
455 : stderr,
456 : JSON_INDENT (2));
457 :
458 14 : jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
459 : is_wallet);
460 14 : if (NULL == jlimits)
461 : {
462 0 : GNUNET_break_op (0);
463 0 : json_decref (jrules);
464 0 : jrules = NULL;
465 0 : return TALER_MHD_reply_with_error (
466 : rc->connection,
467 : MHD_HTTP_INTERNAL_SERVER_ERROR,
468 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
469 : "/kyc-check: rules_to_limits failed");
470 : }
471 14 : json_decref (jrules);
472 14 : jrules = NULL;
473 :
474 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
475 : "KYC limits apply:\n");
476 14 : json_dumpf (jlimits,
477 : stderr,
478 : JSON_INDENT (2));
479 :
480 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
481 : "Returning KYC %s\n",
482 : kyc_required ? "required" : "optional");
483 14 : return TALER_MHD_REPLY_JSON_PACK (
484 : rc->connection,
485 : kyc_required
486 : ? MHD_HTTP_ACCEPTED
487 : : MHD_HTTP_OK,
488 : GNUNET_JSON_pack_bool ("aml_review",
489 : aml_review),
490 : GNUNET_JSON_pack_uint64 ("rule_gen",
491 : rule_gen),
492 : GNUNET_JSON_pack_data_auto ("access_token",
493 : &access_token),
494 : GNUNET_JSON_pack_allow_null (
495 : GNUNET_JSON_pack_array_steal ("limits",
496 : jlimits)));
497 : }
498 :
499 :
500 : /* end of taler-exchange-httpd_kyc-check.c */
|