exchange_api_kyc_check.c (10650B)
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 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_kyc_check.c 19 * @brief Implementation of the /kyc-check request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP check codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_curl_lib.h> 26 #include "taler/taler_exchange_service.h" 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_handle.h" 29 #include "taler/taler_signatures.h" 30 #include "exchange_api_curl_defaults.h" 31 32 33 /** 34 * @brief A ``/kyc-check`` handle 35 */ 36 struct TALER_EXCHANGE_KycCheckHandle 37 { 38 39 /** 40 * The url for this request. 41 */ 42 char *url; 43 44 /** 45 * Handle for the request. 46 */ 47 struct GNUNET_CURL_Job *job; 48 49 /** 50 * Function to call with the result. 51 */ 52 TALER_EXCHANGE_KycStatusCallback cb; 53 54 /** 55 * Closure for @e cb. 56 */ 57 void *cb_cls; 58 59 }; 60 61 62 static enum GNUNET_GenericReturnValue 63 parse_account_status ( 64 struct TALER_EXCHANGE_KycCheckHandle *kch, 65 const json_t *j, 66 struct TALER_EXCHANGE_KycStatus *ks, 67 struct TALER_EXCHANGE_AccountKycStatus *status) 68 { 69 const json_t *limits = NULL; 70 struct GNUNET_JSON_Specification spec[] = { 71 GNUNET_JSON_spec_bool ("aml_review", 72 &status->aml_review), 73 GNUNET_JSON_spec_uint64 ("rule_gen", 74 &status->rule_gen), 75 GNUNET_JSON_spec_fixed_auto ("access_token", 76 &status->access_token), 77 GNUNET_JSON_spec_mark_optional ( 78 GNUNET_JSON_spec_array_const ("limits", 79 &limits), 80 NULL), 81 GNUNET_JSON_spec_end () 82 }; 83 84 if (GNUNET_OK != 85 GNUNET_JSON_parse (j, 86 spec, 87 NULL, NULL)) 88 { 89 GNUNET_break_op (0); 90 return GNUNET_SYSERR; 91 } 92 if ( (NULL != limits) && 93 (0 != json_array_size (limits)) ) 94 { 95 size_t limit_length = json_array_size (limits); 96 struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)]; 97 size_t i; 98 json_t *limit; 99 100 json_array_foreach (limits, i, limit) 101 { 102 struct TALER_EXCHANGE_AccountLimit *al = &ala[i]; 103 struct GNUNET_JSON_Specification ispec[] = { 104 GNUNET_JSON_spec_mark_optional ( 105 GNUNET_JSON_spec_bool ("soft_limit", 106 &al->soft_limit), 107 NULL), 108 GNUNET_JSON_spec_relative_time ("timeframe", 109 &al->timeframe), 110 TALER_JSON_spec_kycte ("operation_type", 111 &al->operation_type), 112 TALER_JSON_spec_amount_any ("threshold", 113 &al->threshold), 114 GNUNET_JSON_spec_end () 115 }; 116 117 al->soft_limit = false; 118 if (GNUNET_OK != 119 GNUNET_JSON_parse (limit, 120 ispec, 121 NULL, NULL)) 122 { 123 GNUNET_break_op (0); 124 return GNUNET_SYSERR; 125 } 126 } 127 status->limits = ala; 128 status->limits_length = limit_length; 129 kch->cb (kch->cb_cls, 130 ks); 131 } 132 else 133 { 134 kch->cb (kch->cb_cls, 135 ks); 136 } 137 GNUNET_JSON_parse_free (spec); 138 return GNUNET_OK; 139 } 140 141 142 /** 143 * Function called when we're done processing the 144 * HTTP /kyc-check request. 145 * 146 * @param cls the `struct TALER_EXCHANGE_KycCheckHandle` 147 * @param response_code HTTP response code, 0 on error 148 * @param response parsed JSON result, NULL on error 149 */ 150 static void 151 handle_kyc_check_finished (void *cls, 152 long response_code, 153 const void *response) 154 { 155 struct TALER_EXCHANGE_KycCheckHandle *kch = cls; 156 const json_t *j = response; 157 struct TALER_EXCHANGE_KycStatus ks = { 158 .hr.reply = j, 159 .hr.http_status = (unsigned int) response_code 160 }; 161 162 kch->job = NULL; 163 switch (response_code) 164 { 165 case 0: 166 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 167 break; 168 case MHD_HTTP_OK: 169 { 170 if (GNUNET_OK != 171 parse_account_status (kch, 172 j, 173 &ks, 174 &ks.details.ok)) 175 { 176 GNUNET_break_op (0); 177 ks.hr.http_status = 0; 178 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 179 break; 180 } 181 TALER_EXCHANGE_kyc_check_cancel (kch); 182 return; 183 } 184 case MHD_HTTP_ACCEPTED: 185 { 186 if (GNUNET_OK != 187 parse_account_status (kch, 188 j, 189 &ks, 190 &ks.details.accepted)) 191 { 192 GNUNET_break_op (0); 193 ks.hr.http_status = 0; 194 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 195 break; 196 } 197 TALER_EXCHANGE_kyc_check_cancel (kch); 198 return; 199 } 200 case MHD_HTTP_NO_CONTENT: 201 break; 202 case MHD_HTTP_BAD_REQUEST: 203 ks.hr.ec = TALER_JSON_get_error_code (j); 204 /* This should never happen, either us or the exchange is buggy 205 (or API version conflict); just pass JSON reply to the application */ 206 break; 207 case MHD_HTTP_FORBIDDEN: 208 { 209 struct GNUNET_JSON_Specification spec[] = { 210 GNUNET_JSON_spec_fixed_auto ( 211 "expected_account_pub", 212 &ks.details.forbidden.expected_account_pub), 213 TALER_JSON_spec_ec ("code", 214 &ks.hr.ec), 215 GNUNET_JSON_spec_end () 216 }; 217 218 if (GNUNET_OK != 219 GNUNET_JSON_parse (j, 220 spec, 221 NULL, NULL)) 222 { 223 GNUNET_break_op (0); 224 ks.hr.http_status = 0; 225 ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 226 break; 227 } 228 break; 229 } 230 case MHD_HTTP_NOT_FOUND: 231 ks.hr.ec = TALER_JSON_get_error_code (j); 232 break; 233 case MHD_HTTP_CONFLICT: 234 ks.hr.ec = TALER_JSON_get_error_code (j); 235 break; 236 case MHD_HTTP_INTERNAL_SERVER_ERROR: 237 ks.hr.ec = TALER_JSON_get_error_code (j); 238 /* Server had an internal issue; we should retry, but this API 239 leaves this to the application */ 240 break; 241 default: 242 /* unexpected response code */ 243 GNUNET_break_op (0); 244 ks.hr.ec = TALER_JSON_get_error_code (j); 245 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 246 "Unexpected response code %u/%d for exchange kyc_check\n", 247 (unsigned int) response_code, 248 (int) ks.hr.ec); 249 break; 250 } 251 kch->cb (kch->cb_cls, 252 &ks); 253 TALER_EXCHANGE_kyc_check_cancel (kch); 254 } 255 256 257 struct TALER_EXCHANGE_KycCheckHandle * 258 TALER_EXCHANGE_kyc_check ( 259 struct GNUNET_CURL_Context *ctx, 260 const char *url, 261 const struct TALER_NormalizedPaytoHashP *h_payto, 262 const union TALER_AccountPrivateKeyP *account_priv, 263 uint64_t known_rule_gen, 264 enum TALER_EXCHANGE_KycLongPollTarget lpt, 265 struct GNUNET_TIME_Relative timeout, 266 TALER_EXCHANGE_KycStatusCallback cb, 267 void *cb_cls) 268 { 269 struct TALER_EXCHANGE_KycCheckHandle *kch; 270 CURL *eh; 271 char arg_str[128]; 272 char timeout_ms[32]; 273 char lpt_str[32]; 274 char krg_str[32]; 275 struct curl_slist *job_headers = NULL; 276 unsigned long long tms; 277 278 { 279 char *hps; 280 281 hps = GNUNET_STRINGS_data_to_string_alloc ( 282 h_payto, 283 sizeof (*h_payto)); 284 GNUNET_snprintf (arg_str, 285 sizeof (arg_str), 286 "kyc-check/%s", 287 hps); 288 GNUNET_free (hps); 289 } 290 tms = timeout.rel_value_us 291 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 292 GNUNET_snprintf (timeout_ms, 293 sizeof (timeout_ms), 294 "%llu", 295 tms); 296 GNUNET_snprintf (krg_str, 297 sizeof (krg_str), 298 "%llu", 299 (unsigned long long) known_rule_gen); 300 GNUNET_snprintf (lpt_str, 301 sizeof (lpt_str), 302 "%d", 303 (int) lpt); 304 kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); 305 kch->cb = cb; 306 kch->cb_cls = cb_cls; 307 kch->url 308 = TALER_url_join ( 309 url, 310 arg_str, 311 "timeout_ms", 312 GNUNET_TIME_relative_is_zero (timeout) 313 ? NULL 314 : timeout_ms, 315 "min_rule", 316 0 == known_rule_gen 317 ? NULL 318 : krg_str, 319 "lpt", 320 TALER_EXCHANGE_KLPT_NONE == lpt 321 ? NULL 322 : lpt_str, 323 NULL); 324 if (NULL == kch->url) 325 { 326 GNUNET_free (kch); 327 return NULL; 328 } 329 eh = TALER_EXCHANGE_curl_easy_get_ (kch->url); 330 if (NULL == eh) 331 { 332 GNUNET_break (0); 333 GNUNET_free (kch->url); 334 GNUNET_free (kch); 335 return NULL; 336 } 337 if (0 != tms) 338 { 339 GNUNET_break (CURLE_OK == 340 curl_easy_setopt (eh, 341 CURLOPT_TIMEOUT_MS, 342 (long) (tms + 500L))); 343 } 344 job_headers 345 = curl_slist_append ( 346 job_headers, 347 "Content-Type: application/json"); 348 { 349 union TALER_AccountSignatureP account_sig; 350 char *sig_hdr; 351 char *hdr; 352 353 TALER_account_kyc_auth_sign (account_priv, 354 &account_sig); 355 356 sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( 357 &account_sig, 358 sizeof (account_sig)); 359 GNUNET_asprintf (&hdr, 360 "%s: %s", 361 TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE, 362 sig_hdr); 363 GNUNET_free (sig_hdr); 364 job_headers = curl_slist_append (job_headers, 365 hdr); 366 GNUNET_free (hdr); 367 if (NULL == job_headers) 368 { 369 GNUNET_break (0); 370 curl_easy_cleanup (eh); 371 return NULL; 372 } 373 } 374 kch->job 375 = GNUNET_CURL_job_add2 (ctx, 376 eh, 377 job_headers, 378 &handle_kyc_check_finished, 379 kch); 380 curl_slist_free_all (job_headers); 381 return kch; 382 } 383 384 385 void 386 TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch) 387 { 388 if (NULL != kch->job) 389 { 390 GNUNET_CURL_job_cancel (kch->job); 391 kch->job = NULL; 392 } 393 GNUNET_free (kch->url); 394 GNUNET_free (kch); 395 } 396 397 398 /* end of exchange_api_kyc_check.c */