exchange_api_lookup_kyc_attributes.c (10863B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 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_lookup_kyc_attributes.c 19 * @brief Implementation of the /aml/$OFFICER_PUB/attributes request 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <microhttpd.h> /* just for HTTP status 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 GET /aml/$OFFICER_PUB/attributes Handle 35 */ 36 struct TALER_EXCHANGE_LookupKycAttributes 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_LookupKycAttributesCallback attributes_cb; 53 54 /** 55 * Closure for @e cb. 56 */ 57 void *attributes_cb_cls; 58 59 /** 60 * HTTP headers for the job. 61 */ 62 struct curl_slist *job_headers; 63 64 }; 65 66 67 /** 68 * Parse AML decision summary array. 69 * 70 * @param[in,out] lh handle to use for allocations 71 * @param jdetails JSON array with AML decision summaries 72 * @param[out] detail_ar where to write the result 73 * @return #GNUNET_OK on success 74 */ 75 static enum GNUNET_GenericReturnValue 76 parse_kyc_attributes ( 77 struct TALER_EXCHANGE_LookupKycAttributes *lh, 78 const json_t *jdetails, 79 struct TALER_EXCHANGE_KycAttributeDetail *detail_ar) 80 { 81 json_t *obj; 82 size_t idx; 83 84 json_array_foreach (jdetails, idx, obj) 85 { 86 struct TALER_EXCHANGE_KycAttributeDetail *detail 87 = &detail_ar[idx]; 88 struct GNUNET_JSON_Specification spec[] = { 89 GNUNET_JSON_spec_uint64 ("rowid", 90 &detail->row_id), 91 GNUNET_JSON_spec_mark_optional ( 92 GNUNET_JSON_spec_string ("provider_name", 93 &detail->provider_name), 94 NULL), 95 GNUNET_JSON_spec_object_const ("attributes", 96 &detail->attributes), 97 GNUNET_JSON_spec_timestamp ("collection_time", 98 &detail->collection_time), 99 GNUNET_JSON_spec_end () 100 }; 101 102 if (GNUNET_OK != 103 GNUNET_JSON_parse (obj, 104 spec, 105 NULL, 106 NULL)) 107 { 108 GNUNET_break_op (0); 109 return GNUNET_SYSERR; 110 } 111 } 112 return GNUNET_OK; 113 } 114 115 116 /** 117 * Parse the provided decision data from the "200 OK" response. 118 * 119 * @param[in,out] lh handle (callback may be zero'ed out) 120 * @param json json reply with the data for one coin 121 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 122 */ 123 static enum GNUNET_GenericReturnValue 124 parse_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh, 125 const json_t *json) 126 { 127 struct TALER_EXCHANGE_KycAttributesResponse lr = { 128 .hr.reply = json, 129 .hr.http_status = MHD_HTTP_OK 130 }; 131 const json_t *jdetails; 132 struct GNUNET_JSON_Specification spec[] = { 133 GNUNET_JSON_spec_array_const ("details", 134 &jdetails), 135 GNUNET_JSON_spec_end () 136 }; 137 138 if (GNUNET_OK != 139 GNUNET_JSON_parse (json, 140 spec, 141 NULL, 142 NULL)) 143 { 144 GNUNET_break_op (0); 145 return GNUNET_SYSERR; 146 } 147 lr.details.ok.kyc_attributes_length 148 = json_array_size (jdetails); 149 { 150 struct TALER_EXCHANGE_KycAttributeDetail details[ 151 GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; 152 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 153 154 memset (details, 155 0, 156 sizeof (details)); 157 lr.details.ok.kyc_attributes = details; 158 ret = parse_kyc_attributes (lh, 159 jdetails, 160 details); 161 if (GNUNET_OK == ret) 162 { 163 lh->attributes_cb (lh->attributes_cb_cls, 164 &lr); 165 lh->attributes_cb = NULL; 166 } 167 return ret; 168 } 169 } 170 171 172 /** 173 * Function called when we're done processing the 174 * HTTP /aml/$OFFICER_PUB/attributes request. 175 * 176 * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes` 177 * @param response_code HTTP response code, 0 on error 178 * @param response parsed JSON result, NULL on error 179 */ 180 static void 181 handle_lookup_finished (void *cls, 182 long response_code, 183 const void *response) 184 { 185 struct TALER_EXCHANGE_LookupKycAttributes *lh = cls; 186 const json_t *j = response; 187 struct TALER_EXCHANGE_KycAttributesResponse lr = { 188 .hr.reply = j, 189 .hr.http_status = (unsigned int) response_code 190 }; 191 192 lh->job = NULL; 193 switch (response_code) 194 { 195 case 0: 196 lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 197 break; 198 case MHD_HTTP_OK: 199 if (GNUNET_OK != 200 parse_attributes_ok (lh, 201 j)) 202 { 203 GNUNET_break_op (0); 204 lr.hr.http_status = 0; 205 lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 206 break; 207 } 208 GNUNET_assert (NULL == lh->attributes_cb); 209 TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); 210 return; 211 case MHD_HTTP_NO_CONTENT: 212 break; 213 case MHD_HTTP_BAD_REQUEST: 214 lr.hr.ec = TALER_JSON_get_error_code (j); 215 lr.hr.hint = TALER_JSON_get_error_hint (j); 216 /* This should never happen, either us or the exchange is buggy 217 (or API version conflict); just pass JSON reply to the application */ 218 break; 219 case MHD_HTTP_FORBIDDEN: 220 lr.hr.ec = TALER_JSON_get_error_code (j); 221 lr.hr.hint = TALER_JSON_get_error_hint (j); 222 /* Nothing really to verify, exchange says this coin was not melted; we 223 should pass the JSON reply to the application */ 224 break; 225 case MHD_HTTP_NOT_FOUND: 226 lr.hr.ec = TALER_JSON_get_error_code (j); 227 lr.hr.hint = TALER_JSON_get_error_hint (j); 228 /* Nothing really to verify, exchange says this coin was not melted; we 229 should pass the JSON reply to the application */ 230 break; 231 case MHD_HTTP_INTERNAL_SERVER_ERROR: 232 lr.hr.ec = TALER_JSON_get_error_code (j); 233 lr.hr.hint = TALER_JSON_get_error_hint (j); 234 /* Server had an internal issue; we should retry, but this API 235 leaves this to the application */ 236 break; 237 default: 238 /* unexpected response code */ 239 GNUNET_break_op (0); 240 lr.hr.ec = TALER_JSON_get_error_code (j); 241 lr.hr.hint = TALER_JSON_get_error_hint (j); 242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 243 "Unexpected response code %u/%d for lookup AML attributes\n", 244 (unsigned int) response_code, 245 (int) lr.hr.ec); 246 break; 247 } 248 if (NULL != lh->attributes_cb) 249 lh->attributes_cb (lh->attributes_cb_cls, 250 &lr); 251 TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); 252 } 253 254 255 struct TALER_EXCHANGE_LookupKycAttributes * 256 TALER_EXCHANGE_lookup_kyc_attributes ( 257 struct GNUNET_CURL_Context *ctx, 258 const char *exchange_url, 259 const struct TALER_NormalizedPaytoHashP *h_payto, 260 uint64_t offset, 261 int64_t limit, 262 const struct TALER_AmlOfficerPrivateKeyP *officer_priv, 263 TALER_EXCHANGE_LookupKycAttributesCallback cb, 264 void *cb_cls) 265 { 266 struct TALER_EXCHANGE_LookupKycAttributes *lh; 267 CURL *eh; 268 struct TALER_AmlOfficerPublicKeyP officer_pub; 269 struct TALER_AmlOfficerSignatureP officer_sig; 270 char arg_str[sizeof (officer_pub) * 2 271 + sizeof (*h_payto) * 2 272 + 32]; 273 274 GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, 275 &officer_pub.eddsa_pub); 276 TALER_officer_aml_query_sign (officer_priv, 277 &officer_sig); 278 279 { 280 char payto_s[sizeof (*h_payto) * 2]; 281 char pub_str[sizeof (officer_pub) * 2]; 282 char *end; 283 284 end = GNUNET_STRINGS_data_to_string ( 285 h_payto, 286 sizeof (*h_payto), 287 payto_s, 288 sizeof (payto_s)); 289 *end = '\0'; 290 end = GNUNET_STRINGS_data_to_string ( 291 &officer_pub, 292 sizeof (officer_pub), 293 pub_str, 294 sizeof (pub_str)); 295 *end = '\0'; 296 GNUNET_snprintf (arg_str, 297 sizeof (arg_str), 298 "aml/%s/attributes/%s", 299 pub_str, 300 payto_s); 301 } 302 lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes); 303 lh->attributes_cb = cb; 304 lh->attributes_cb_cls = cb_cls; 305 { 306 char limit_s[24]; 307 char offset_s[24]; 308 309 GNUNET_snprintf (limit_s, 310 sizeof (limit_s), 311 "%lld", 312 (long long) limit); 313 GNUNET_snprintf (offset_s, 314 sizeof (offset_s), 315 "%llu", 316 (unsigned long long) offset); 317 lh->url = TALER_url_join ( 318 exchange_url, 319 arg_str, 320 "limit", 321 limit_s, 322 "offset", 323 offset_s, 324 "h_payto", 325 NULL); 326 } 327 if (NULL == lh->url) 328 { 329 GNUNET_free (lh); 330 return NULL; 331 } 332 eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); 333 if (NULL == eh) 334 { 335 GNUNET_break (0); 336 GNUNET_free (lh->url); 337 GNUNET_free (lh); 338 return NULL; 339 } 340 { 341 char *hdr; 342 char sig_str[sizeof (officer_sig) * 2]; 343 char *end; 344 345 end = GNUNET_STRINGS_data_to_string ( 346 &officer_sig, 347 sizeof (officer_sig), 348 sig_str, 349 sizeof (sig_str)); 350 *end = '\0'; 351 352 GNUNET_asprintf (&hdr, 353 "%s: %s", 354 TALER_AML_OFFICER_SIGNATURE_HEADER, 355 sig_str); 356 lh->job_headers = curl_slist_append (NULL, 357 hdr); 358 GNUNET_free (hdr); 359 lh->job_headers = curl_slist_append (lh->job_headers, 360 "Content-type: application/json"); 361 lh->job = GNUNET_CURL_job_add2 (ctx, 362 eh, 363 lh->job_headers, 364 &handle_lookup_finished, 365 lh); 366 } 367 return lh; 368 } 369 370 371 void 372 TALER_EXCHANGE_lookup_kyc_attributes_cancel ( 373 struct TALER_EXCHANGE_LookupKycAttributes *lh) 374 { 375 if (NULL != lh->job) 376 { 377 GNUNET_CURL_job_cancel (lh->job); 378 lh->job = NULL; 379 } 380 curl_slist_free_all (lh->job_headers); 381 GNUNET_free (lh->url); 382 GNUNET_free (lh); 383 } 384 385 386 /* end of exchange_api_lookup_kyc_attributes.c */